代码之家  ›  专栏  ›  技术社区  ›  Daniel May

C 4.0中的一般方差

  •  16
  • Daniel May  · 技术社区  · 14 年前

    C 4.0中的一般方差的实现方式是,可以在不发生异常的情况下编写以下内容(C 3.0中会发生这种情况):

     List<int> intList = new List<int>();
     List<object> objectList = intList; 
    

    [非功能性示例:参见Jon Skeet的答案]

    我最近参加了一个会议,乔恩·斯基特对一般方差进行了极好的概述,但我不确定我是否完全理解它——我理解 in out 当涉及到对反和协变时,我会用关键词,但我很好奇在幕后会发生什么。

    当执行此代码时,CLR会看到什么? 它是否隐式转换了 List<int> List<object> 或者它只是内置的,我们现在可以在派生类型之间转换为父类型?

    出于兴趣,为什么在以前的版本中没有引入这种方法?它的主要好处是什么——即真实世界的使用?

    有关此的详细信息 post 对于一般的差异(但问题是非常过时的,寻找真实的、最新的信息)

    3 回复  |  直到 13 年前
        1
  •  20
  •   Jon Skeet    14 年前

    不,您的示例不起作用的原因有三个:

    • 类(例如 List<T> )是不变的;只有委托和接口是可变的
    • 为了使方差起作用,接口只能在一个方向上使用类型参数(In表示反向方差,Out表示协方差)
    • 不支持将值类型作为方差的类型参数-因此没有来自的转换 IEnumerable<int> IEnumerable<object> 例如

    (代码无法在C 3.0和4.0中编译-没有例外。)

    所以这个 工作:

    IEnumerable<string> strings = new List<string>();
    IEnumerable<object> objects = strings;
    

    clr只使用引用,未更改-不创建新对象。所以如果你打电话来 objects.GetType() 你还是会得到 List<string> .

    我相信它之前没有被引入,因为语言设计者仍然需要解决如何公开它的细节——它从V2开始就在CLR中。

    这些好处与其他时候相同,在这些时候您希望能够使用一种类型作为另一种类型。用上周六我用过的同样的例子,如果你有工具的话 IComparer<Shape> 为了按面积比较形状,你不能用它来排序 List<Circle> -如果它可以比较任何两个形状,它当然可以比较任何两个圆。从C 4开始,会有一个从 i比较器<形状> IComparer<Circle> 所以你可以打电话 circles.Sort(areaComparer) .

        2
  •  15
  •   Eric Lippert    14 年前

    一些额外的想法。

    当执行此代码时,clr看到了什么

    正如Jon和其他人正确地指出的,我们在类上并没有做变化,只有接口和委托。所以在您的示例中,clr什么也看不到;代码无法编译。如果您通过插入足够的强制转换来编译它,那么它会在运行时崩溃,并出现错误的转换异常。

    现在,当差异发挥作用时,在幕后如何工作仍然是一个合理的问题。答案是:我们将其限制为参数化接口和委托类型的引用类型参数的原因是 没有什么 发生在幕后。当你说

    object x = "hello";
    

    在幕后发生的是,对字符串的引用被固定在类型为object的变量中 无需修改 . 构成对字符串引用的位是合法的对对象引用的位,因此这里不需要发生任何事情。clr只是停止将这些位看作是一个字符串,并开始将它们看作是一个对象。

    当你说:

    IEnumerator<string> e1 = whatever;
    IEnumerator<object> e2 = e1;
    

    同样的事情。什么都没发生。引用字符串枚举器的位与引用对象枚举器的位相同。当你做演员的时候,会有更多的魔法在起作用,比如:

    IEnumerator<string> e1 = whatever;
    IEnumerator<object> e2 = (IEnumerator<object>)(object)e1;
    

    现在,clr必须生成一个检查,确保e1确实实现了该接口,并且该检查必须能够智能地识别差异。

    但是我们可以摆脱变量接口的原因是没有OP转换 因为 常规分配兼容性就是这样。你打算用e2做什么?

    object z = e2.Current;
    

    返回引用字符串的位。我们已经确定,这些与对象是兼容的,没有变化。

    为什么不早点介绍?我们还有其他功能要做,而且预算有限。

    原则上的好处是什么?从字符串序列转换为对象序列“只是工作”。

        3
  •  8
  •   Community Egal    7 年前

    出于兴趣,为什么不 在以前的版本中引入

    .NET的第一个版本(1.x)根本没有泛型,所以泛型差异还远远没有。

    应该注意,在.NET的所有版本中,都存在数组协方差。不幸的是,这是不安全的协方差:

    Apple[] apples = new [] { apple1, apple2 };
    Fruit[] fruit = apples;
    fruit[1] = new Orange(); // Oh snap! Runtime exception! Can't store an orange in an array of apples!
    

    C 4中的协方差和逆方差是安全的,可以防止这个问题。

    主要的好处是什么? 世界用法?

    在代码中,您多次调用API,希望使用放大的基类型(例如 IEnumerable<Base> )但你所得到的只是一种衍生的放大类型(例如 IEnumerable<Derived> )

    在C 2和C 3中,您需要手动转换为 IEnumerable<基础> 即使它应该“只起作用”。协变和逆变使得它“只是工作”。

    P.S.真是糟透了,斯基特的回答是把我所有的重复点都吃了。该死的,双向飞碟!:-)看起来他 answered this before 但是。