Простые примеры взаимодействия и контравариантности - PullRequest
40 голосов
/ 12 января 2011

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

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

Ответы [ 3 ]

87 голосов
/ 12 января 2011

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

Понятия не имею, что означает «противоинвариантность».В остальном все просто.

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

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>, этот код успешно выполняется в C # 4;в C # 3 это не удалось бы, потому что ковариация на IEnumerable<T> не работала в C # 3.

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

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

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

Делегат Action<T> является контравариантным.Тот факт, что Frog конвертируется в Animal, подразумевает, что Action<Animal> конвертируется в Action<Frog>.Обратите внимание, что это отношение является противоположным направлением ковариантного;вот почему это "против" вариант.Из-за конвертируемости этот код успешно выполняется;это не помогло бы в C # 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/

4 голосов
/ 12 января 2011

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

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

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

Думая о них как о типовых параметрах только для ввода или только для вывода, легче понять IMO.

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

2 голосов
/ 12 января 2011

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

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>>();

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

...