ковариация в c # - PullRequest
       26

ковариация в c #

19 голосов
/ 28 октября 2010

Можно ли разыграть List<Subclass> до List<Superclass> в C # 4.0?

Что-то в этом роде:

class joe : human {}

List<joe> joes = GetJoes();

List<human> humanJoes = joes;

Разве не для этого нужна ковариация?

, если вы можете сделать:

human h = joe1 as human;

почемуесли бы вы не смогли сделать

List<human> humans = joes as List<human>; 

, чем было бы нелегально делать (Джо) людей [0], потому что этот предмет был заброшен ... и все были бы счастливы.Теперь единственной альтернативой является создание нового списка

Ответы [ 5 ]

26 голосов
/ 28 октября 2010

Вы не можете сделать это, потому что это не будет безопасно. Рассмотрим:

List<Joe> joes = GetJoes();    
List<Human> humanJoes = joes;
humanJoes.Clear();
humanJoes.Add(new Fred());
Joe joe = joes[0];

Очевидно, что последняя строка (если не более ранняя) имеет ошибку , поскольку Fred не является Joe. Инвариантность List<T> предотвращает эту ошибку при времени компиляции вместо времени выполнения.

6 голосов
/ 28 октября 2010

Создание нового списка людей, который принимает joes в качестве ввода:

List<human> humanJoes = new List<human>(joes);
2 голосов
/ 28 октября 2010

Нет.Совместные / контравариантные функции C # 4.0 поддерживают только интерфейсы и делегаты.Не поддерживают конкретные типы, такие как List<T>.

1 голос
/ 28 октября 2010

Нет. Как сказал Джаред, функции C / 4.0 в C / 4.0 поддерживают только интерфейсы и делегаты. Однако он также не работает с IList<T>, и причина в том, что IList<T> содержит методы для добавления и изменения элементов в списке - как говорится в новом ответе Джона Скита.

Единственный способ привести список "joe" к "human" - это если интерфейс предназначен только для чтения, что-то вроде этого:

public interface IListReader<out T> : IEnumerable<T>
{
    T this[int index] { get; }
    int Count { get; }
}

Даже метод Contains(T item) не будет разрешен, потому что когда вы преобразуете IListReader<joe> в IListReader<human>, в IListReader<joe>.

отсутствует метод Contains(human item).

Вы могли бы «форсировать» приведение от IList<joe> до IListReader<joe>, IListReader<human> или даже IList<human>, используя GoInterface . Но если список достаточно мал для копирования, более простое решение - просто скопировать его в новый List<human>, как указывал Paw.

0 голосов
/ 28 октября 2010

Если я позволю вашему List<Joe> joes быть обобщенным как ...

  List<Human> humans = joes;

... две ссылки humans и joes теперь указывают на один и тот же список. Код, следующий за вышеприведенным присвоением, не может предотвратить вставку / добавление экземпляр другого типа человека, скажем, водопроводчик, в список. Учитывая, что class Plumber: Human {}

humans.Add(new Plumber()); // Add() now accepts any Human not just a Joe 

список, на который ссылается humans, теперь содержит как джо, так и сантехников. Обратите внимание, что на тот же объект списка все еще ссылается ссылка joes. Теперь, если я воспользуюсь ссылкой joes для чтения из объекта списка, я мог бы вывести водопроводчика вместо joe . Сантехник и Джо, как известно, неявно взаимопревращаемы ... поэтому мое получение водопроводчика вместо Джо из списка нарушает безопасность типов. Слесарь-сантехник, конечно, не приветствуется через ссылку на список joes.

Однако в последних версиях C # его можно обойти это ограничение для универсального класса / коллекции, реализовав универсальный интерфейс, параметр типа которого имеет модификатор out. Скажем, у нас теперь есть ABag<T> : ICovariable<out T>. Модификатор out ограничивает T только выходными позициями (например, тип возвращаемых методов). Вы не можете ввести T в сумку. Вы можете только прочитать их из этого. Это позволяет нам обобщать joes для ICovariable<Human>, не беспокоясь о вставке в него сантехника, так как интерфейс этого не позволяет. Теперь мы можем написать ...

ICovariable<Human> humans = joes ; // now its good !
humans.Add(new Plumber()); // error 
...