zoukankan      html  css  js  c++  java
  • 13.3.5 【接口和委托的泛型可变性】限制和说明

    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 语句和字段风格的事件。

  • 相关阅读:
    Sublimit 3 注册码
    Flink sql 之 两阶段聚合与 TwoStageOptimizedAggregateRule(源码分析)
    IDM使用教程:利用IDM下载百度网盘文件
    Python调用Prometheus监控数据并计算
    Windows搭建Nginx+PHP+MySQL环境(图文详解)
    PHPstorm+XDebug+Chrome/Firefox超详细教程(图文)
    C# 教你怎么解决DateTime转unix时间戳出现的误差
    asp.net win IIS下cookie读取不区分大小写如何处理
    SQL 空格分隔的多个关键词模糊查询货品资料任一匹配和全部匹配 数据库实现方式
    TDBGrideh表头自动排序设置
  • 原文地址:https://www.cnblogs.com/kikyoqiang/p/10111638.html
Copyright © 2011-2022 走看看