Простые примеры co и контравариантности
Может ли кто-нибудь предоставить мне простые примеры С# для конвариантности, контравариантности, инвариантности и contra-инвариантности (если такая вещь существует).
Все образцы, которые я видел до сих пор, просто вставляли какой-то объект в System.Object
.
Ответы
Ответ 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>>();
Этот код теперь компилируется, аргументы типа такие же, как и выше.