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

Naw*_*waz 6 c# arrays generics covariance

在他的深度C#中,Jon Skeet试图回答以下问题:

为什么我不能转换List<string>List<object>

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

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

正如注释所读,第一个编译好,但第二个编译错误.我真的不明白原因.Jon Skeet解释了这一点,只说第一个编译,因为在.NET中,数组是协变的,第二个不编译,因为泛型不是协变的(相反,它们是不变的).此外,数组在.NET中是协变的,因为数组在Java和.NET中是协变的,因此它与Java类似.

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

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

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

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

vcs*_*nes 10

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

Eric Lippert在博客文章中详细介绍了这一点.

有趣的一点:

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

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

由于Giraffe小于Animal,并且"make a array of"是对类型的协变操作,因此Giraffe []小于Animal [],因此一个实例适合该变量.

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

重点是我自己.


Eri*_*ert 7

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

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

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

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

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

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