Why Animals[] animals = new Cat[5] compiles, but List<Animal> animals = new List<Cat>() does not?
In his book C# in Depth, Jon Skeet tries to answer the following question:
Why can't I convert
List<string>
toList<object>
?
To explain it, he started with a code-snippet, which includes these two lines:
Animal[] animals = new Cat[5]; //Ok. Compiles fine!
List<Animal> animals = new List<Cat>(); //Compilation error!
As the comments read, the first one compiles fine, but the second one gives compilation error. I didn't really understand the reason. Jon Skeet explained this with saying only that first one compiles because in .NET, arrays are covariant, and second one doesn't compile because generics are not covariant (they're开发者_StackOverflow中文版 invariant, instead). And furthermore, arrays are covariant in .NET, because arrays are covariant in Java and .NET made it similar to Java.
I'm not completely statisfied with this short-answer. I want to know it in a more detail, with some insight into how compiler handles the difference, and how it generates IL and all.
Also, if I write (taken from the book itself):
Animal[] animals = new Cat[5]; //Ok. Compiles fine!
animals.Add(new Turtle()); //this too compiles fine!
It compiles fine, but fails at runtime. If it has to fail at runtime(which means what I write shouldn't make sense), then why does it compile in the first place? Can I use the instance animals
in my code, and which also runs with no runtime error?
Arrays have a weird history with variance in .NET. Proper support for variance was added in the 2.0 version of the CLR - and the language in C# 4.0. Arrays however, have always had a covariant behavior.
Eric Lippert goes into great detail on that in a blog post.
The interesting bit:
Ever since C# 1.0, arrays where the element type is a reference type are covariant. This is perfectly legal:
Animal[] animals = new Giraffe[10];
Since Giraffe is smaller than Animal, and “make an array of” is a covariant operation on types, Giraffe[] is smaller than Animal[], so an instance fits into that variable.
Unfortunately, this particular kind of covariance is broken. It was added to the CLR because Java requires it and the CLR designers wanted to be able to support Java-like languages. We then up and added it to C# because it was in the CLR. This decision was quite controversial at the time and I am not very happy about it, but there’s nothing we can do about it now.
Emphasis added by myself.
If it has to fail at runtime, then why does it compile in the first place?
That's precisely why array covariance is broken. The fact that we allow it means that we allow what should be an error that gets caught at compilation time to be ignored, and instead get caught at runtime.
I'm not completely statisfied with this short-answer. I want to know it in a more detail, with some insight into how compiler handles the difference ...
The compiler handles the difference easily enough. The compiler has a whole bunch of code that determines when one type is compatible with another. Part of that code deals with array-to-array conversions. Part of that code deals with generic-to-generic conversions. The code is a straightforward translation of the relevant lines of the specification.
... and how it generates IL and all.
There is no need to generate any IL whatsoever for a covariant array conversion. Why would we need to generate IL for a conversion between two reference-compatible types? It's like asking what IL we generate for converting a string to an object. A string already is an object, so there's no code generated.
I think Jon Skeet explained it rather well, however if you need that "Ahah" moment consider how generics work.
A generic class like List<> is, for most purposes, treated externally as a normal class. e.g. when you say
List<string>()
the compiler saysListString()
(which contains strings) and the compiler can't be smart enough to convert a ListString to a ListObject by casting the items of its internal collection.
From reading MSDN blog post, it appears that covariance and contravariance is supported in .NET 4.0 when when working with delegates and interfaces. It mentions Eric's articles also in the second sentence.
精彩评论