Почему Animals [] animals = new Cat [5] компилируется, а List <Animal>animals = new List <Cat>() - нет? - PullRequest
6 голосов
/ 28 сентября 2011

В своей книге C # в глубине Джон Скит пытается ответить на следующий вопрос:

Почему я не могу преобразовать List<string> в List<object>?

Чтобы объяснить это, он начал с фрагмента кода, который включает в себя следующие две строки:

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

По мере чтения комментариев первая прекрасно компилируется , но второй дает ошибка компиляции .Я не очень понял причину.Джон Скит объяснил это тем, что сказал, что первый компилируется, потому что в .NET массивы ковариантны, а второй не компилируется, потому что генерики не ковариантны (вместо этого они инвариантны).И, кроме того, массивы ковариантны в .NET, потому что массивы ковариантны в Java, а .NET делает его похожим на Java.

Я не полностью удовлетворен этим кратким ответом.Я хочу узнать это более подробно, с некоторым пониманием того, как компилятор обрабатывает разницу, и как он генерирует IL и все.

Кроме того, если я напишу (взято из самой книги):

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

Он хорошо компилируется, но не работает во время выполнения.Если он должен потерпеть неудачу во время выполнения (что означает, что то, что я пишу, не имеет смысла), то почему он компилируется в первую очередь?Могу ли я использовать экземпляр animals в своем коде, который также работает без ошибок времени выполнения?

Ответы [ 3 ]

10 голосов
/ 28 сентября 2011

Массивы имеют странную историю с отклонениями в .NET. Правильная поддержка дисперсии была добавлена ​​в версии CLR 2.0 - и в языке C # 4.0. Однако массивы всегда имели ковариантное поведение.

Эрик Липперт подробно рассказывает об этом в блоге .

Интересный бит:

Начиная с C # 1.0, массивы, в которых тип элемента является ссылочным типом, являются ковариантными. Это совершенно законно:

Животное [] Животные = Новый Жираф [10];

Поскольку Giraffe меньше, чем Animal, и «создать массив» является ковариантной операцией над типами, Giraffe [] меньше, чем Animal [], поэтому экземпляр вписывается в эту переменную.

К сожалению, этот конкретный вид ковариации нарушен. Он был добавлен в CLR, потому что Java требует этого, и разработчики CLR хотели иметь возможность поддерживать языки, подобные Java. Затем мы добавили его в C #, потому что он был в CLR. Это решение было довольно спорным в то время, и я не очень доволен этим, но сейчас мы ничего не можем с этим поделать.

Акцент добавлен мной.

7 голосов
/ 28 сентября 2011

Если произойдет сбой во время выполнения, то почему он компилируется в первую очередь?

Именно поэтому ковариация массива нарушена .Тот факт, что мы это разрешаем, означает, что мы допускаем игнорирование ошибки, возникающей во время компиляции, и вместо этого ловимся во время выполнения.-ответ.Я хочу узнать это более подробно, с некоторым пониманием того, как компилятор обрабатывает разницу ...

Компилятор обрабатывает разницу достаточно легко.Компилятор имеет целый набор кода, который определяет, когда один тип совместим с другим.Часть этого кода имеет дело с преобразованиями массива в массив.Часть этого кода имеет дело с общими преобразованиями в общие.Код представляет собой простой перевод соответствующих строк спецификации.

... и как он генерирует IL и все.

Нет необходимости генерировать какие-либо IL для преобразования ковариантного массива. Зачем нам нужно генерировать IL для преобразования между двумя ссылочно-совместимыми типами? Это все равно, что спрашивать, какой IL мы генерируем для преобразования строки в объект. Строка уже является объектом , поэтому код не генерируется.

0 голосов
/ 28 сентября 2011

Я думаю, что Джон Скит объяснил это довольно хорошо, однако, если вам нужен этот момент "Ахах", рассмотрите как генерики работают.

Универсальный класс, такой как List <>, в большинстве случаев рассматривается как обычный класс.например, когда вы говорите List<string>(), компилятор говорит ListString() (который содержит строки), и компилятор не может быть достаточно умным, чтобы преобразовать ListString в ListObject путем приведения элементов его внутренней коллекции.

Из прочтения в блоге MSDN выясняется, что ковариация и контравариантность поддерживаются в .NET 4.0 при при при работе с делегатами и интерфейсами.Он упоминает статьи Эрика также во втором предложении.

...