为什么动物[]动物=新猫[5]编译,但列表<动物>动物=新列表<猫>()不?</cat> </animal>

时间:2011-09-28 19:01:48

标签: c# arrays generics covariance

在他的书 C#in Depth 中,Jon Skeet试图回答以下问题:

  

为什么我无法将List<string>转换为List<object>

为了解释它,他开始使用代码片段,其中包括以下两行:

Animal[] animals = new Cat[5]; //Ok. Compiles fine!            
List<Animal> animals = new List<Cat>(); //Compilation error!

正如评论所述,第一个compiles fine,但第二个给出了compilation error。我真的不明白原因。 Jon Skeet解释了这一点,只说第一个编译,因为在.NET中,数组是协变的,第二个不编译,因为泛型不是协变的(相反,它们是不变的)。此外,数组在.NET中是协变的,因为数组在Java和.NET中是协变的,因此它与Java类似。

我对这个简短的答案并不完全满意。我想更详细地了解它,并深入了解编译器如何处理差异,以及它如何生成IL和所有。

另外,如果我写(摘自书本身):

Animal[] animals = new Cat[5]; //Ok. Compiles fine!
animals.Add(new Turtle()); //this too compiles fine!

编译很好,但在运行时失败。如果它必须在运行时失败(这意味着我写的东西应该没有意义),那为什么它首先编译?我可以在我的代码中使用实例animals,并且运行时没有运行时错误吗?

3 个答案:

答案 0 :(得分:10)

阵列有一个奇怪的历史,在.NET中存在差异。在2.0版本的CLR中添加了对方差的适当支持 - 以及C#4.0中的语言。然而,数组总是具有协变行为。

Eric Lippert在blog post中详细介绍了这一点。

有趣的一点:

  

自C#1.0以来,元素类型为引用类型的数组是协变的。这完全合法:

     

动物[]动物=新长颈鹿[10];

     

由于Giraffe小于Animal,而“make a array of”是对类型的协变操作,Giraffe []小于Animal [],因此一个实例适合该变量。

     

不幸的是,这种特殊的协方差被打破了。 它被添加到CLR中,因为Java需要它,而CLR设计者希望能够支持类似Java的语言。然后我们将它添加到C#,因为它在CLR中。这个决定在当时颇具争议,我对此并不十分满意,但现在我们无能为力。

我自己强调的重点。

答案 1 :(得分:7)

  

如果它必须在运行时失败,那么为什么它首先编译?

这正是阵列协方差已破坏的原因。我们允许它的事实意味着我们允许忽略在编译时被捕获的错误,而不是在运行时被捕获。

  

我对这个简短的答案并不完全满意。我想更详细地了解它,并深入了解编译器如何处理差异......

编译器可以轻松处理差异。编译器有一大堆代码,用于确定一种类型何时与另一种类型兼容。部分代码处理数组到数组的转换。该代码的一部分涉及泛型到泛型的转换。代码是规范相关行的直接翻译。

  

......以及它如何产生IL和所有。

无需为协变阵列转换生成任何IL。 为什么我们需要为两个参考兼容类型之间的转换生成IL?这就像询问我们生成的用于将字符串转换为对象的IL。 字符串已经是对象,因此没有生成代码。

答案 2 :(得分:0)

我认为Jon Skeet解释得相当不错,但如果您需要“Ahah”时刻,请考虑 泛型如何工作。

  

像List&lt;&gt;这样的通用类对于大多数目的而言,是对外治疗   作为一个普通的班级。例如当你说编译器说List<string>()时   ListString()(包含字符串)并且编译器不能智能   足以通过强制转换项目将ListString转换为ListObject   它的内部集合。

从阅读MSDN blog post开始,当 使用委托和接口时, .NET 4.0 支持协方差和逆变。它在第二句中也提到了埃里克的文章。