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

Простые примеры co и контравариантности

Может ли кто-нибудь предоставить мне простые примеры С# для конвариантности, контравариантности, инвариантности и contra-инвариантности (если такая вещь существует).

Все образцы, которые я видел до сих пор, просто вставляли какой-то объект в System.Object.

4b9b3361

Ответ 1

Может ли кто-нибудь предоставить мне простые примеры С# для конвариантности, контравариантности, инвариантности и contra-инвариантности (если такая вещь существует).

Я понятия не имею, что означает "противоположная инвариантность". Остальное легко.

Здесь приведен пример ковариации:

void FeedTheAnimals(IEnumerable<Animal> animals) 
{ 
    foreach(Animal animal in animals)
        animal.Feed();
}
...
List<Giraffe> giraffes = ...;
FeedTheAnimals(giraffes);

Интерфейс IEnumerable<T> является ковариантным. Тот факт, что Жираф является конвертируемым в Animal, подразумевает, что IEnumerable<Giraffe> преобразуется в IEnumerable<Animal>. Поскольку List<Giraffe> реализует IEnumerable<Giraffe>, этот код преуспевает в С# 4; он потерпел бы неудачу в С# 3, потому что ковариация на IEnumerable<T> не работала на С# 3.

Это должно иметь смысл. Последовательность Жирафов можно рассматривать как последовательность животных.

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

void DoSomethingToAFrog(Action<Frog> action, Frog frog)
{
    action(frog);
}
...
Action<Animal> feed = animal=>{animal.Feed();}
DoSomethingToAFrog(feed, new Frog());

Делегат Action<T> контравариантен. Тот факт, что Лягушка конвертируется в Animal, подразумевает, что Action<Animal> можно конвертировать в Action<Frog>. Обратите внимание, как это отношение является противоположным направлением ковариантной; что это вариант "contra". Из-за конвертируемости этот код преуспевает; это не сработало бы на С# 3.

Это должно иметь смысл. Действие может принимать любое Животное; нам нужно действие, которое может взять любую лягушку, и действие, которое может взять любое Животное, конечно же, может также взять любую лягушку.

Пример инвариантности:

void ReadAndWrite(IList<Mammal> mammals)
{
    Mammal mammal = mammals[0];
    mammals[0] = new Tiger();
}

Можем ли мы передать IList<Giraffe> на эту вещь? Нет, потому что кто-то собирается писать в него Тигра, а тигр не может быть в списке Жирафов. Можем ли мы передать IList<Animal> в эту вещь? Нет, потому что мы собираемся прочитать Млекопитающее, и список животных может содержать лягушку. IList<T> является инвариантным. Его можно использовать только как то, что на самом деле есть.

Дополнительные сведения о дизайне этой функции см. в моей серии статей о том, как мы ее проектировали и строили.

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

Ответ 2

Инвариантность (в этом контексте) - это отсутствие как ко- и contra-дисперсии. Таким образом, контринвариантность не имеет смысла. Любой параметр типа, который не помечен как in или out, является инвариантным. Это означает, что этот параметр типа может быть использован и возвращен.

Хорошим примером ковариации является IEnumerable<out T>, потому что очевидно IEnumerable<Derived> можно заменить на IEnumerable<Base>. Или Func<out T>, который возвращает значения типа T.
Например, IEnumerable<Dog> можно преобразовать в IEnumerable<Animal>, потому что любая Собака является животным.

Для противоположной дисперсии вы можете использовать любой потребительский интерфейс или делегат. IComparer<in T> или Action<in T> приходят мне на ум. Они никогда не возвращают переменную типа T, а получают ее только. И где вы, кроме как получить Base, можете передать Derived.

Размышление о них как параметрах ввода или только для вывода облегчает понимание IMO.

И слово Invariants обычно не используется вместе с дисперсией типа, но с инвариантами класса или метода и представляет собой сохраняемое свойство. См. этот поток стеков, где обсуждаются различия между инвариантами и инвариантностью.

Ответ 3

Если вы рассматриваете регулярное использование дженериков, вы регулярно используете интерфейс для обработки объекта, но объект является экземпляром класса - вы не можете создать экземпляр интерфейса. Используйте простой список строк в качестве примера.

IList<string> strlist = new List<string>();

Я уверен, что вы знаете о преимуществах использования IList<> вместо прямого использования List<>. Он позволяет инвертировать управление, и вы можете решить, что не хотите использовать List<> больше, но вместо этого вы хотите LinkedList<>. Вышеприведенный код работает нормально, потому что общий тип интерфейса и класса тот же: string.

Это может немного усложниться, если вы хотите составить список списка строк. Рассмотрим этот пример:

IList<IList<string>> strlists = new List<List<string>>();

Это явно не скомпилируется, потому что общие аргументы типа IList<string> и List<string> не совпадают. Даже если вы объявили внешний список как обычный класс, например List<IList<string>>, он не будет компилироваться - аргументы типа не совпадают.

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

IEnumerable<IList<string>> strlists = new List<List<string>>();

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