为什么Animals[] Animals = new Cat[5]编译,而Listanimals = n
本文关键字:Animals Animal animals List 编译 Cat new 为什么 | 更新日期: 2023-09-27 18:09:54
在他的书 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!
如注释所示,第一个编译良好,但第二个给出编译错误。我真的不明白其中的原因。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
,并且它也没有运行时错误?
数组在。net中有一个奇怪的变化历史。在CLR的2.0版本中加入了对变量的适当支持,而在c# 4.0中加入了该语言。然而,数组总是具有协变行为。
Eric Lippert在一篇博客文章中对此做了详细的阐述。
有趣的部分:
从c# 1.0开始,元素类型为引用类型的数组是协变的。这是完全合法的:
Animal[] animals = new Giraffe[10];
因为Giraffe比Animal小,而"make a array of"是对类型的协变操作,Giraffe[]比Animal[]小,所以实例适合于该变量
不幸的是,这种特殊的协方差被打破了。它被添加到CLR中,因为Java需要它,CLR设计者希望能够支持类Java语言。然后我们把它添加到c#中,因为它在CLR中。这个决定当时很有争议,我对此很不高兴,但现在我们也无能为力了。
自我强调
如果它必须在运行时失败,那么为什么它首先要编译?
这就是数组协方差破碎的原因。我们允许这样做的事实意味着我们允许在编译时捕获的错误被忽略,而不是在运行时捕获。
我对这个简短的回答不太满意。我想更详细地了解它,并深入了解编译器如何处理差异……
编译器很容易处理这种差异。编译器有一大堆代码来确定一种类型何时与另一种类型兼容。该代码的一部分处理数组到数组的转换。该代码的一部分处理泛型到泛型的转换。代码是规范相关行的直接翻译。
…以及它是如何产生IL的
对于协变数组转换,不需要生成任何IL。为什么我们需要为两个引用兼容类型之间的转换生成IL ?这就像问我们将字符串转换为对象生成什么IL一样。字符串已经是对象,所以没有生成代码。
我认为Jon Skeet解释得很好,但是如果您需要"啊哈"时刻,请考虑泛型是如何工作的。
在大多数情况下,像List<>这样的泛型类是在外部处理的作为一个普通的班级。例如,当你说List<string>()
时,编译器会说ListString()
(它包含字符串)和编译器不能聪明的项强制转换,足以将ListString转换为ListObject它的内部集合
从阅读MSDN博客文章来看,似乎在中支持协方差和逆变。当处理委托和接口时。第二句也提到了Eric的文章。