IList <T>и List <T>Преобразования с интерфейсами - PullRequest
6 голосов
/ 01 июня 2011

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

В этом примере Cat реализует IAnimal и, конечно, Список реализует IList :

IList<IAnimal> cats = new List<Cat>();

, но генерирует ошибку компиляции (Не удается неявно преобразовать тип ...) .Это также не сработает, если я использую суперкласс asbtract [Animal], от которого наследуется Cat.Тем не менее, если я заменим IAnimal на Cat :

IList<Cat> cats = new List<Cat>();

, то все будет хорошо.

На мой взгляд, потому что Cat реализует IAnimal , первый пример должен быть приемлемым, что позволяет нам возвращать интерфейс как для списка, так и для содержимого типа.

Может кто-нибудь объяснить, почему он не действителен?Я уверен, что есть логическое объяснение.

Ответы [ 6 ]

20 голосов
/ 01 июня 2011

Есть логическое объяснение, и этот точный вопрос задают почти каждый день в StackOverflow.

Предположим, это было законно:

IList<IAnimal> cats = new List<Cat>(); 

Что мешает этому быть законным?

cats.Add(new Giraffe());

Ничего.«кошки» - это список животных, а жираф - это животное, и поэтому вы можете добавить жирафа в список кошек.

Очевидно, что это небезопасно.

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

IEnumerable<IAnimal> cats = new List<Cat>(); 

, потому что IEnumerable<IAnimal> не имеет метода Add, поэтому нет способа нарушить безопасность типов.

Смотрите мою серию статей о том, как мыразработал эту функцию в C # 4 для получения более подробной информации.

http://blogs.msdn.com/b/ericlippert/archive/tags/covariance+and+contravariance/default.aspx

(Начните снизу).

6 голосов
/ 01 июня 2011

C # не поддерживает такого рода отклонения на IList<T> по соображениям безопасности типов.

Если C # действительно поддерживает это, что вы ожидаете здесь?

IList<IAnimal> cats = new List<Cat>();

cats.Add(new Dog());         // a dog is an IAnimal too
cats.Add(new Squirrel());    // and so is a squirrel

В C # 4 вы можете сделать что-то вроде этого:

IEnumerable<IAnimal> cats = new List<Cat>();

Это потому, что интерфейс IEnumerable<T> поддерживает дисперсию такого рода.IEnumerable<T> является последовательностью только для чтения, поэтому вы не сможете впоследствии добавить Dog или Squirrel к IEnumerable<IAnimal>, который фактически является списком Cat.

1 голос
/ 01 июня 2011

IList<T> не является ковариантным интерфейсом (или это будет IList<out T>). Это связано с тем, что IList принимает тип T в качестве параметра и возвращает его как возвращаемое значение из методов, что делает проблематичным ковариацию.

Например, если в вашем примере:

IList<IAnimal> cats = new List<Cat>();

Вы хотели добавить нового кота, из котов, это позволило бы:

cats.Add(new Dog());

при условии, что Dog также реализовал IAnimal, это явно неверно и не сработает Вот почему IList не является ковариантным или контравариантным интерфейсом.

1 голос
/ 01 июня 2011

Вы можете достичь этого, используя LINQ:

IList<IAnimal> cats = new List<Cat>().Cast<IAnimal>();
1 голос
/ 01 июня 2011

Этот тип ковариация не поддерживается в C # 4.0. Разумно ожидать поведение, которое вы хотите, оно просто не поддерживается (пока).

0 голосов
/ 03 июня 2011

Если вам нужна ковариация и контравариантность в интерфейсе, подобном списку, вы должны определить интерфейсы IReadableList и IWritableList и получить тип из List , который реализует как ReadableList . Это позволит затем передать NewList в процедуру, которая ожидает ReadableList или WritableList .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...