2010-02-05 59 views
16

一般差异在C#4.0已经以这样一种方式,它可以编写没有例外以下(这是C#3.0中会发生什么)来实现:一般差异在C#4.0

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

[例非功能性:参见乔恩斯基特的答案]

我最近参加了一个会议,其中乔恩斯基特给通用差异的很好的概述,但我不知道我完全得到它 - 我理解的意义的inout关键词当谈到对立和协变时,但我很好奇幕后发生的事情。

当代码执行时,CLR看到了什么?它是否将List<int>隐含地转换为List<object>还是仅仅构建,我们现在可以在派生类型之间转换为父类型?

出于兴趣,为什么在之前的版本中没有介绍它,以及主要优点 - 即真实世界的用法?在这个post用于通用方差

更多信息(但问题是非常过时的,寻找真正的,向上的最新信息)

回答

20

没有,你将不会是原因有三个工作:

  • 类(如List<T>)是不变的;只有代表和接口是变体
  • 为了方差起作用,接口只能在一个方向上使用类型参数(in为反变换,out为协方差)
  • 值类型不支持作为方差的类型参数 - 所以没有从IEnumerable<int> converstion到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开始,将会有一个从IComparer<Shape>IComparer<Circle>的逆变换,因此您可以拨打circles.Sort(areaComparer)

+0

啊。我将不得不下载并观看上周六的例子,并亲自体验一下。当然,这个概念本身是有道理的 - 只是试图让我的头脑在真实世界中使用这个概念的思想。非常感谢回复。 – 2010-02-05 15:13:40

+0

@丹尼尔:没问题 - 对不起,我明确没有在周六足够好的解释:)(当然有很多东西需要弥补......) – 2010-02-05 15:23:03

+0

哦,这根本就不是那个乔恩 - 它全部移动了一点快,我还没有接触到C#4的任何新功能 - 我像疯子一样乱写笔记。看起来我必须订购第二版C#的深度:) – 2010-02-05 15:25:22

8

出于兴趣,为什么不这样 在以前的版本

的第一个版本(1.x中).NET的没有泛型可言,所以一般的方差远介绍关闭。

应该指出的是,在.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<Base>。合作和反方差使其“只是工作”。

p.s.完全吮吸Skeet的答案是吃掉我所有的代表点。该死的你,Skeet! :-)虽然他看起来像是answered this before

+1

需要说明的是:CLI和协变性总是支持的(其中“always”意为“至少从v2开始”)。它只是在C#4.0之前没有在C#中暴露*。但是,例如Eiffel.NET一直支持它,尽管AFAIK库没有正确注释。 (不知道为什么,实际上,创建一个IL重写器工具并不太容易,它只需要一个共同和反变化的接口列表,并在元数据中翻转正确的位,即使你可以过去不用C#编写,BCL就是用这种编写的。) – 2010-02-05 19:23:02

14

一些额外的想法。

时执行该代码

随着乔恩和其他人正确地指出,我们不是在类,只有接口和委托方做什么的CLR见。所以在你的例子中,CLR什么也看不到;该代码不能编译。如果通过插入足够的强制转换来强制它进行编译,那么它会在运行时崩溃并导致错误的强制转换异常。

现在,当问题发生时,如何在幕后进行变化仍然是一个合理的问题。答案是:我们限制这个参数化参数化接口和委托类型的类型参数的原因是,没有任何发生在幕后。当你说

object x = "hello"; 

会发生什么幕后是参考串扎进类型的对象的变量不加修改。构成对字符串的引用的位是合法位,以便作为对象的引用,所以在这里不需要发生任何事情。 CLR只是停止将这些位作为引用字符串的思路,并开始将它们想象为引用一个对象。

当你说:

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

同样的事情。什么都没发生。引用字符串枚举器的位与引用对象枚举器的位相同。有几分当你做一个演员,其中进场更神奇,说:

现在CLR必须产生一个检查,实际上E1确实实现了该接口,并检查必须是聪明承认差异。

但是,我们可以通过不同的接口实现无操作转换的原因是,因为定期分配兼容性就是这种方式。你打算使用e2作什么?

object z = e2.Current; 

返回对字符串的引用的位。我们已经确定,这些与对象兼容而不会改变。

为什么前面没介绍过?我们还有其他功能要做,预算有限。

原理好处是什么?从字符串序列到对象序列的转换“正常工作”。注意到啊,