Подтвердить что ты не робот

Ковариация с С# Generics

Учитывая интерфейс IQuestion и реализацию этого интерфейса AMQuestion, предположим следующий пример:

List<AMQuestion> typed = new List<AMQuestion>();
IList<IQuestion> nonTyped = typed;

В этом примере получается, как и ожидалось, ошибка компиляции, указывающая на то, что два не одного типа. Но в нем указано, что существует явное преобразование. Поэтому я изменяю его так:

List<AMQuestion> typed = new List<AMQuestion>();
IList<IQuestion> nonTyped = typed as IList<IQuestion>;

Которая затем компилируется, но во время выполнения nonTyped всегда имеет значение null. Если кто-то может объяснить две вещи:

  • Почему это не работает.
  • Как я могу добиться желаемого эффекта.

Было бы очень благодарно. Спасибо!

4b9b3361

Ответ 1

Тот факт, что AMQuestion реализует интерфейс IQuestion, не преобразуется в List<AMQuestion> из List<IQuestion>.

Поскольку этот приведение является незаконным, оператор as возвращает null.

Вы должны отнести каждый элемент отдельно:

IList<IQuestion> nonTyped = typed.Cast<IQuestion>().ToList();

Что касается вашего комментария, рассмотрите следующий код с обычными примерами животных-клише:

//Lizard and Donkey inherit from Animal
List<Lizard> lizards = new List<Lizard> { new Lizard() };
List<Donkey> donkeys = new List<Donkey> { new Donkey() };

List<Animal> animals = lizards as List<Animal>; //let pretend this doesn't return null
animals.Add(new Donkey()); //Reality unravels!

если нам было разрешено использовать List<Lizard> для List<Animal>, тогда мы могли бы теоретически добавить в этот список новый Donkey, который нарушил бы наследование.

Ответ 2

Почему это не работает: as возвращает null, если динамический тип значения не может быть присвоен целевому типу, а List<AMQuestion> не может быть отправлен на IList<IQuestion>.

Но почему это не так? Ну, проверьте это:

List<AMQuestion> typed = new List<AMQuestion>();
IList<IQuestion> nonTyped = typed as IList<IQuestion>;
nonTyped.Add(new OTQuestion());
AMQuestion whaaaat = typed[0];

IList<IQuestion> говорит: "Вы можете добавить мне все IQuestion". Но это обещание, которое оно не могло удержать, если бы оно было List<AMQuestion>.

Теперь, если вы не хотели ничего добавлять, просто просмотрите его как коллекцию IQuestion -совместимых вещей, тогда лучше всего сделать это с помощью IReadOnlyList<IQuestion> с List.AsReadOnly. Поскольку список, доступный только для чтения, не может иметь странных вещей, добавленных в него, он может быть правильно установлен.

Ответ 3

Проблема заключается в том, что List<AMQuestion> нельзя отнести к IList<IQuestion>, поэтому использование оператора as не помогает. Явное преобразование в этом случае означает лить AMQuestion в IQuestion:

IList<IQuestion> nonTyped = typed.Cast<IQuestion>.ToList();

Кстати, у вас есть термин "ковариация" в вашем названии. В IList тип не является ковариантным. Именно поэтому актерский состав не существует. Причина в том, что интерфейс IList имеет T в некоторых параметрах и в некоторых возвращаемых значениях, поэтому для T нельзя использовать in и out. (@Sneftel имеет хороший пример, чтобы показать, почему это приведение не допускается.)

Если вам нужно только прочитать из списка, вы можете вместо этого использовать IEnumerable:

IEnumerable<IQuestion> = typed;

Это будет работать, потому что IEnumerable<out T> имеет out, потому что вы не можете передать ему параметр T as. Обычно вы должны сделать самое слабое "обещание" в своем коде, чтобы оно было расширяемым.

Ответ 4

IList<T> не ковариантно для T; это не может быть, поскольку интерфейс определяет функции, которые принимают значения типа T в позиции "ввода". Однако IEnumerable<T> ковариантно для T. Если вы можете ограничить свой тип IEnumerable<T>, вы можете сделать это:

List<AMQuestion> typed = new List<AMQuestion>();
IEnumerable<IQuestion> nonTyped = typed;

Это не делает никаких преобразований в списке.

Причина, по которой вы не можете преобразовать List<AMQuestion> в List<IQuestion> (при условии, что AMQuestion реализует интерфейс), заключается в том, что для таких функций, как List<T>.Add, должно быть несколько проверок времени выполнения, чтобы убедиться, что вы действительно добавляете AMQuestion.

Ответ 5

Оператор "as" всегда будет возвращать значение null там, где не существует допустимого действия - это определено поведение. Вы должны преобразовать или сделать список следующим образом:

IList<IQuestion> nonTyped = typed.Cast<IQuestion>().ToList();

Ответ 6

Тип с типичным параметром типа может быть только ковариантным, если этот общий тип встречается только в обращениях чтения и контравариантности, если он встречается только в обращениях к записи. IList<T> позволяет как читать, так и записывать доступ к значениям типа T, поэтому он не может быть вариантом!

Предположим, что вам разрешено присваивать List<AMQuestion> переменной типа IList<IQuestion>. Теперь давайте реализуем class XYQuestion : IQuestion и вставляем значение этого типа в наш IList<IQuestion>, который кажется совершенно законным. Этот список по-прежнему ссылается на List<AMQuestion>, но мы не можем вставить XYQuestion в List<AMQuestion>! Поэтому два типа списка не совместимы с назначением.

IList<IQuestion> list = new List<AMQuestion>(); // Not allowed!
list.Add(new XYQuestion()); // Uuups!

Ответ 7

Поскольку List<T> не является закрытым классом, возможно существование типа, который наследует от List<AMQuestion> и реализует IList<IQuestion>. Если вы не реализуете такой тип самостоятельно, крайне маловероятно, что он когда-либо будет существовать. Тем не менее, было бы вполне законно говорить, например,

class SillyList : List<AMQuestion>, IList<IQuestion> { ... }

и явно реализовать все члены типа IList<IQuestion>. Таким образом, было бы также вполне законно говорить: "Если эта переменная содержит ссылку на экземпляр типа, полученного из List<AMQuestion>, и если этот тип экземпляра также реализует IList<IQuestion>, преобразуйте ссылку на последний тип.