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

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

Можно ли отличить List<Subclass> до List<Superclass> в С# 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>; 

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

4b9b3361

Ответ 1

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

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

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

Ответ 2

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

List<human> humanJoes = new List<human>(joes);

Ответ 3

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

Ответ 4

Нет. Как сказал Джаред, функции co/contravariance С# 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>, как указывала Пау.

Ответ 5

Если я позволю вашему 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, так и plumbers. Обратите внимание, что этот же объект списка все еще упоминается ссылкой joes. Теперь, если я использую ссылку joes для чтения из объекта списка, я могу вытащить водопроводчика вместо joe. Водопроводчик и Джо, как известно, неявно взаимопревращаемы... поэтому мое получение водопроводчика вместо joe из списка разрушает безопасность типа. Водопроводчик, безусловно, не приветствуется ссылкой на список joes.

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

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