1. 不支持类的类型参数的可变性

只有接口和委托可以拥有可变的类型参数。即使类中包含只用于输入(或只用于输出)的类
型参数,仍然不能为它们指定 in 或 out 修饰符。例如, IComparer<T> 的公共实现 Comparer<T>
是不变的——不能将 Comparer<IShape> 转换为 Comparer<Circle> 。
除了实现方面的困难,从理论上看来也应该是这样的。接口是一种从特定视角观察对象的方
式,而类则更多地植根于对象的实际类型。不可否认,继承可以将一个对象视为它继承层次结构
中任何类的实例,由此在一定程度上削弱了这种理由的说服力。但不管怎样,CLR不允许这么做。

2. 可变性只支持引用转换

你不能对任意两个类型参数使用可变性,因为在它们之间会产生转换。这种转换必须为引用
转换。基本上,这使转换只能操作引用类型,并且不能影响引用的二进制表示。因此,编译器知
道操作是类型安全的,并且不会在任何地方插入实际的转换代码。我们在13.3.2节提到过,可变
转换本身是引用转换,所以不会有任何额外的代码。
特别地,这种限制禁止任何值类型转换和用户定义的转换。比如下面的转换是无效的。
 将 IEnumerable<int> 转换为 IEnumerable<object> ——装箱转换;
 将 IEnumerable<short> 转换为 IEnumerable<int> ——值类型转换;
 将 IEnumerable<string> 转换为 IEnumerable<XName> ——用户定义的转换。
用户定义的转换比较少见,因此不成什么问题,但对值类型的限制可能会令你痛苦万分。

3.  out 参数不是输出参数

这曾让我大为诧异,尽管事后看来是有道理的。考虑使用以方法定义的委托:

        delegate bool TryParser<T>(string input, out T value);

你可能会认为 T 可以是协变的——毕竟它只用在输出位置,是这样吗?
CLR并不真正了解 out 参数。在它看来, out 参数只是应用了 [Out] 特性的 ref 参数。C#以明
确赋值的方式为该特性附加了特殊的含义,但CLR没有。并且 ref 参数意味着数据是双向的,因
此如果类型 T 为 ref 参数,也就意味着 T 是不变的。
事实上,即使CLR支持 out 参数,也仍然不安全,因为它可用于方法本身的输入位置;写入
变量之后,同样也可以从中读取它。如果将 out 参数看成是“运行时复制值”似乎好一些,但它
本质上是实参和参数的别名,如果不是完全相同的类型,将会产生问题。由于稍微有些繁琐,此
处不再演示,但本书的网站上可以看到有关示例。
委托和接口使用 out 参数的情况很少,因此这可能不会对你产生影响,但为了以防万一,还
是有必要了解的。

4. 可变性必须显式指定

在介绍表示可变性的语法时(即对类型参数使用 in 或 out 修饰符),你可能会问为什么要这
么麻烦。编译器可以检查正在使用的可变性是否有效,因此为什么不能自动应用呢?
这样可以——至少在很多情况下是可以的——但我宁愿它不可以。通常我们向接口添加方法
时,只会影响实现,而不会影响调用者。但如果声明了一个可变的类型参数,然后又添加了一个
破坏这种可变性的方法,所有的调用者都会受影响。这会造成混乱不堪的局面。可变性要求你对
未来发生的事情考虑周全,并且强迫开发者显式指定修饰符,鼓励他们在执行可变性之前做到心
中有数。
对于委托来说,这种显式的特性就没有那么多争论了:任何对签名所做的影响可变性的修改,
都会破坏已有的使用。但如果在接口的定义中指定了可变性的修饰符,而在委托声明中不指定,
则会显得很奇怪,因此要保持它们的一致性。

5. 注意破坏性修改

每当新的转换可用时,当前代码都有被破坏的风险。例如,如果你依赖于不允许可变性的
is 或 as 操作符的结果,运行在.NET 4时,代码的行为将有所不同。同样,在某些情况下,因为
有了更多可用的选项,重载决策也会选择不同的方法。因此这也成了另一个显式指定可变性的理
由:降低代码被破坏的风险。
这些情况应该是很少见的,而且可变性的优点也比潜在的缺点更加重要。你已经有了单元测
试,可以捕获那些微小的变化,对不对?严肃地说,C#团队对于代码破损的态度非常认真,但有
时引入新特性难免会破坏代码。

6. 多播委托与可变性不能混用

通常情况下,对于泛型来说,除非涉及强制转换,否则不用担心执行时遇到类型安全问题。
不幸的是,当多个可变委托类型组合到一起时,情况就比较讨厌了。用代码可以更好地描述:

            Func<string> stringFunc = () => "";Func<object> objectFunc = () => new object();Func<object> combind = objectFunc + stringFunc;

这段代码可以通过编译,因为将 Func<string> 类型的表达式转换为 Func<object> 是协变
的引用转换。但对象本身仍然为 Func<string >,并且实际进行处理的 Delegate.Combine 方法
要求参数必须为相同的类型——否则它将无法确定要创建什么类型的委托。因此以上代码在执行
时会抛出 ArgumentException 。
这个问题在.NET 4快发布的时候才被发现,但微软察觉到了,并且很可能会在未来的版本中
予以解决(.NET 4.5中还未得到解决)。在此之前的应对之策是:基于可变委托新建一个类型正
确的委托对象,然后再与同一类型的另一个委托进行组合。例如,略微修改之前的代码即可使其工作:

            Func<string> stringFunc = () => "";Func<object> objectFunc = () => new object();Func<object> defensiveCopy = new Func<object>(stringFunc);Func<object> combind = objectFunc + defensiveCopy;

庆幸的是,以我的经验来说,这种情况很少见。

7. 不存在调用者指定的可变性,也不存在部分可变性

与其他问题相比,这个问题的确更能引起你的兴趣,但值得注意的是,C#的可变性与Java
系统相去甚远。Java的泛型可变性相当灵活,它从另一侧面来解决问题:不在类型本身声明可变
性,而是在使用类型的代码处表示所需的可变性。

例如,Java的 List<T> 接口大体上相当于C#的 IList<T> 。它包含添加和提取项的方法,这
在C#中显然是不变的,而在Java中,你可以在调用代码时声明类型来说明所需的可变性。然后编
译器会阻止你使用具有相反可变性的成员。例如,以下代码是完全合法的

            List<Shape> shapes1 = new ArrayList<Shape>();List <? super Square > squares = shapes1;       //声明为逆变的squares.add(new Square(10, 10, 20, 20));List<Circle> circles = new ArrayList<Circle>();circles.Add(new Circle(10, 10, 20));List <? extends Shape > Shapes2 = circles;      //声明为协变的Shape shape = Shapes2.get(0);

我在很大程度上更倾向于C#泛型,而不是Java泛型。特别是类型擦除(type erasure)在很
多时候会让你痛苦万分。但我发现这种处理可变性的方式真的很有趣。我认为C#未来版本中不会
出现类似的东西,所以你应该仔细考虑如何在不增加复杂性的前提下,将接口分割以增加灵活性。
在结束本章之前,还要介绍两处几乎是微不足道的改变——编译器如何处理 lock 语句和字段风格的事件。

转载于:https://www.cnblogs.com/kikyoqiang/p/10111638.html

13.3.5 【接口和委托的泛型可变性】限制和说明相关推荐

  1. 第五节:泛型(泛型类、接口、方法、委托、泛型约束、泛型缓存、逆变和协变)

    一. 泛型诞生的背景 在介绍背景之前,先来看一个案例,要求:分别输出实体model1.model2.model3的id和name值,这三个实体有相同的属性名字id和name. 1 public cla ...

  2. [ASP.NET入门随想七]主角与配角——OO思想的多态、接口与委托

    [ASP.NET入门随想七]主角与配角 --OO思想的多态.接口与委托 曾几何时,我们的父辈接受到的对美好生活的描绘是:"楼上楼下.电灯电话",三十年过去了,电灯电话早已已成为我们 ...

  3. java 接口的泛型方法_Java泛型/泛型方法/通配符/泛型接口/泛型泛型擦出

    从JDK1.5以后引入了三大常用新特性:泛型.枚举(enum).注解(Annotation).其中JDK1.5中泛型是一件非常重要的实现技术,它可以帮助我们解决程序的参数转换问题.本文为大家详细介绍一 ...

  4. C# 委托 (一)—— 委托、 泛型委托与Lambda表达式

    C# 委托 (一)-- 委托. 泛型委托与Lambda表达式 2018年08月19日 20:46:47 wnvalentin 阅读数 2992 版权声明:此文乃博主之原创.鄙人才疏,望大侠斧正.此文可 ...

  5. kotlin学习笔记——接口与委托

    kotlin中的接口比java7中的要强大很多,与java8的很相似. java7中的接口只能定义行为,不能实现. kotlin中的接口也可以实现函数,但是与类的区别是它们是无状态(stateless ...

  6. 韩顺平循序渐进学java 第13讲 抽象类.接口

    13.1抽象类 13.1.1 概念 当父类的一些方法不能确定时,可以用abstract关键字来修饰该方法,称为抽象方法,用abstract来修饰该类,称为抽象类. 13.1.2 抽象类-深入讨论 抽象 ...

  7. 分享券商通达信下单接口执行委托下单过程

    首先要有券商通达信下单接口,如果有这个功能,那么就可以了.利用委托下单功能来设置,这是预埋单的性质,当达到条件时,会自动交易. 整个委托下单过程如下: // 委托下单 // category:  0= ...

  8. JAVA常用基础知识点[继承,抽象,接口,静态,枚举,反射,泛型,多线程...]

    类的继承 Java只支持单继承,不允许多重继承 - 一个子类只能有一个父类 - 一个父类可以派生出多个子类 这里写图片描述 子类继承了父类,就继承了父类的方法和属性. 在子类中,可以使用父类中定义的方 ...

  9. JAVA常用基础知识点[继承,抽象,接口,静态,枚举,反射,泛型,多线程.]

    类的继承 Java只支持单继承,不允许多重继承  - 一个子类只能有一个父类  - 一个父类可以派生出多个子类    子类继承了父类,就继承了父类的方法和属性.  在子类中,可以使用父类中定义的方法和 ...

  10. 利用委托和泛型实现树的常用操作

    在日常开发中,经常遇到对树的操作,我们可以利用泛型和委托对这些树进行操作,这样就不需要每有一个树就要实现相应的功能了. 源码在http://files.cnblogs.com/haiconc/Lang ...

最新文章

  1. 成功解决ValueError: Unable to add relationship because child variable ‘ID‘ in ‘cats_df‘ is also its inde
  2. ECSHOP发送邮件提示need rcpt command的解决方法
  3. jedisPool.getResource()方法长时间无响应并且不报错
  4. 配置根目录_npm配置文件package.json里面的字段你知道多少
  5. [JavaWeb-Bootstrap]Bootstrap响应式布局
  6. UPS分类:直流UPS和交流UPS
  7. Shell 企业29道面试题 [转]
  8. debug模式的开关与功能 django
  9. radio选中事件怎么绑定_Vue双向绑定
  10. linux服务器跟踪命令,Linux下使用strace命令来跟踪.htaccess的使用
  11. 结构方程模型-调节(干扰)效应检验(一)
  12. python3.7操作kafka_python操作kafka
  13. GTK+实现linux聊天室代码详解-clientr端
  14. 压力传感器与数据采集
  15. 1.什么是bat文件
  16. 吴恩达机器学习ex2-logistic regression python版
  17. cocosbuilder详细使用教程
  18. UI界面视觉设计之版式布局
  19. __attribute__ 详解 1
  20. Unity文字转语音

热门文章

  1. 系统学习深度学习(三十一)--Nature DQN(NIPS 2015)
  2. python得安什么安装包_初学 Python 需要安装哪些软件?
  3. pmos低电平驱动_三极管和MOS管驱动电路的正确用法
  4. mate10是否支持html,NFC功能是什么 华为Mate 10支持NFC功能吗【详解】
  5. 无法解析 org.apache.commons:commons-pool2:2.4.2
  6. Tomcat6.0连接器源码分析3
  7. MYSQL主从复制操作文档
  8. C#基于两种需求向图片添加水印
  9. 搭建Open××× Server路由模式、证书认证
  10. fedora 11 下分析系统性能瓶颈之(一)mpstat