Почему я могу отбросить неизменность IList <T>? - PullRequest
5 голосов
/ 26 октября 2011

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

IList<Form> formsList = new List<Form> { new Form(), new Form() };
IList<Control> controlsList = formsList;

Да, это, конечно, невозможно, так как IList (Of T) инвариантен (по крайней мере, моя мысль). Компилятор говорит мне, что:

Невозможно неявно преобразовать тип System.Collections.Generic.IList<System.Windows.Forms.Form> до System.Collections.Generic.IList<System.Windows.Forms.Control>. существует явное преобразование (вам не хватает приведения?)

Хм, значит ли это, что я могу форсировать явное преобразование? Я только что попробовал:

IList<Form> formsList = new List<Form> { new Form(), new Form() };
IList<Control> controlsList = (IList<Control>)formsList;

И ... он компилируется! Значит ли это, что я могу отбросить инвариантность? - По крайней мере с компилятором все в порядке, но я просто превратил предыдущую ошибку времени компиляции в ошибку времени выполнения:

Unable to cast object of type 'System.Collections.Generic.List`1[System.Windows.Forms.Form]' to type 'System.Collections.Generic.IList`1[System.Windows.Forms.Control]'.

Мой вопрос (ы): Почему я могу отбросить инвариантность IList<T> (или любого другого инвариантного интерфейса в отношении моих экспериментов)? Действительно ли я отбрасываю инвариантность, или какое преобразование происходит здесь (поскольку IList(Of Form) и IList(Of Control) совершенно не связаны)? Это темный угол C #, я не знал?

Ответы [ 2 ]

6 голосов
/ 26 октября 2011

По сути, тип может реализовывать IList<Control> , а также IList<Form>, поэтому возможно для успешного приведения - поэтому компилятор позволяет ему в настоящее время (за исключением: это могло бы быть более умным здесь и произвести предупреждение, потому что оно знает конкретный тип объекта, на который ссылаются, но не знает. Я не думаю, что было бы целесообразно выдавать ошибку компилятора, так как это не принципиальное изменение для типа для реализации нового интерфейса).

В качестве примера такого типа:

public class EvilList : IList<Form>, IList<Control> { ... }

То, что происходит во время выполнения, является просто проверкой типа CLR. Исключение, которое вы видите, является показателем сбоя этой операции.

IL, сгенерированный для броска:

castclass [mscorlib]System.Collections.Generic.IList`1<class [System.Windows.Forms]System.Windows.Forms.Control>

С MSDN :

Инструкция castclass пытается привести ссылку на объект (тип О) поверх стека к указанному классу. Новый класс определяется токен метаданных, указывающий желаемый класс. Если класс объект на вершине стека не реализует новый класс (при условии, что новый класс является интерфейсом) и не является производным классом новый класс затем InvalidCastException бросается. Если объект ссылка является нулевой ссылкой, класс CastClass успешно и возвращает новый объект как нулевая ссылка.

InvalidCastException генерируется, если obj не может быть приведен к классу.

1 голос
/ 26 октября 2011

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

IList<Form> formsList = new List<Form> { new Form(), new Form() }; 
IList<Control> controlsList = (IList<Control>)formsList; 
controlsList.Add(New TextBlock); // Should throw at runtime.

Тип безопасной инвариантности обычно поднимает свою голову как исключение времени выполнения в этом случае.В этом случае может быть безопасно объявить controlsList как IEnumerable, а не IList (при условии .Net 4.0), поскольку IEnumerable объявлен как ковариантный (IEnumerable).Это решает проблему попытки добавить неправильный тип в список элементов управления, потому что .Add (и другие методы ввода) не доступны из интерфейса out.

...