Why animals [] animals = new Cat [5] compiles, but List <Animal> animals = new List <Cat> () not?

In his C # in Depth book, John Skeet tries to answer the following question:

Why can't I convert List<string> to List<object> ?

To explain this, he started with a piece of code that includes the following two lines:

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

As you can see from the comments, the first compiles fine , but the second gives a compilation error . I did not quite understand the reason. John Skeet explained this by saying that the former compiles because arrays are covariant in .NET, and the latter does not compile because generics are not covariant (they are invariant instead). And besides, arrays are covariant in .NET because arrays are covariant in Java and .NET made it look like Java.

I do not fully agree with this short answer. I want to know more about this, with some understanding of how the compiler deals with the difference and how it generates IL and that’s it.

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 does not work at runtime. If it should fail at runtime (which means that I'm writing, that doesn't make sense), then why does it compile in the first place? Can I use an instance of animals in my code and which also works without a runtime error?

+6
source share
3 answers

Arrays have a weird history with deviations in .NET. In version 2.0 of the CLR, proper dispersion support and language were added in C # 4.0. However, arrays have always had covariant behavior.

Eric Lippert details this in a post.

Interesting bit:

Since C # 1.0, arrays where the element type is a reference type are covariant. This is completely legal:

Animal [] animals = new Giraffe [10];

Since the Giraffe is smaller than Animal, and the "make array of" is a covariant operation on types, Giraffe [] is smaller than Animal [], so the instance fits into this variable.

Unfortunately, this type of covariance is broken. It was added to the CLR because Java requires this, and CLR developers wanted to be able to support Java-like languages. Then we added it to C # because it was in the CLR. At that time, this decision was quite controversial, and I am not very happy with it, but now we can do nothing about it.

Accent added by me.

+10
source

If at runtime it fails, then why is it compiled in the first place?

That is why the covariance of the array is broken. The fact that we resolve this means that we resolve what should be an error that gets caught at compile time to be ignored, but instead caught at run time.

I do not fully agree with this short answer. I want to know more about this, with some understanding of how the compiler deals with the difference ...

The compiler can easily handle the difference. The compiler has a whole bunch of code that determines when one type is compatible with another. Part of this code relates to transforming an array into an array. Part of this code is related to general conversions. The code is a simple translation of the relevant specification lines.

... and how it generates IL and all.

There is no need to generate any IL to convert the covariant array. Why do we need to generate IL to convert between two reference compatible types? . As if we were asking which IL we were generating to convert a string to an object. The string is already an object, so no code is generated.

+7
source

I think John Skeet explained it pretty well, however, if you need this moment of β€œAha” to consider how generics work.

A general class, such as List <>, is treated from the outside as a normal class for most purposes. for example, when you say List<string>() , the compiler says ListString() (which contains the strings), and the compiler cannot be smart enough to convert a ListString into a ListObject, casting items from its internal collection.

From reading the MSDN blog post , it seems that covariance and contravariance are supported in .NET 4.0 when working with delegates and interfaces. He mentions Eric's articles also in the second sentence.

0
source

Source: https://habr.com/ru/post/898229/


All Articles