У меня небольшие проблемы с пониманием того, как я буду использовать ковариацию и контравариантность в реальном мире.
До сих пор я видел только один и тот же старый пример массива.
object[] objectArray = new string[] { "string 1", "string 2" };
Было бы неплохо увидеть пример, который позволил бы мне использовать его во время моей разработки, если бы я мог видеть, что он используется в другом месте.
c#
c#-4.0
covariance
бритва
источник
источник
Ответы:
Допустим, у вас есть класс Person и класс, производный от него, Учитель. У вас есть некоторые операции, которые принимают в
IEnumerable<Person>
качестве аргумента. В вашем классе школы у вас есть метод, который возвращаетIEnumerable<Teacher>
. Ковариантность позволяет вам напрямую использовать этот результат для методов, которыеIEnumerable<Person>
заменяют более производный тип менее производным (более универсальным) типом. Контравариантность, напротив, позволяет вам использовать более общий тип, где указывается более производный тип.См. Также Ковариантность и Контравариантность в обобщениях на MSDN .
Классы :
Использование :
источник
Для полноты ...
источник
void feed(IGobbler<Donkey> dg)
. Если вместо этого вы взяли IGobbler <Quadruped>, вы не сможете передать дракона, который ест только ослов.Вот что я собрал, чтобы помочь мне понять разницу
tldr
источник
Contravariance
примере), когдаFruit
родительApple
?Ключевые слова in и out управляют правилами приведения компилятора для интерфейсов и делегатов с общими параметрами:
источник
Вот простой пример использования иерархии наследования.
Учитывая простую иерархию классов:
И в коде:
Инвариантность (т.е. параметры общего типа * не * украшены
in
илиout
ключевыми словами)По-видимому, такой метод, как этот
... должен принять гетерогенную коллекцию: (что он делает)
Однако передать коллекцию более производного типа не удается!
Зачем? Поскольку универсальный параметр
IList<LifeForm>
не является ковариантным -IList<T>
он инвариантен, поэтомуIList<LifeForm>
принимает только те коллекции (которые реализуют IList) там, гдеT
должен быть параметризованный типLifeForm
.Если реализация метода
PrintLifeForms
была вредоносной (но имеет такую же сигнатуру метода), причина, по которой компилятор предотвращает передачу,List<Giraffe>
становится очевидной:Так как
IList
допускает добавление или удаление элементов, любой подклассLifeForm
может, таким образом, быть добавлен к параметруlifeForms
и будет нарушать тип любой коллекции производных типов, передаваемых методу. (Здесь злонамеренный метод попытается добавитьZebra
кvar myGiraffes
). К счастью, компилятор защищает нас от этой опасности.Ковариантность (универсальный с параметризованным типом, украшенным
out
)Ковариантность широко используется с неизменяемыми коллекциями (т. Е. Когда новые элементы не могут быть добавлены или удалены из коллекции)
Решение приведенного выше примера состоит в том, чтобы гарантировать использование ковариантного универсального типа коллекции, например
IEnumerable
(определяется какIEnumerable<out T>
).IEnumerable
не имеет методов для изменения коллекции, и в результатеout
ковариации любая коллекция с подтипомLifeForm
теперь может быть передана методу:PrintLifeForms
теперь может быть вызванZebras
,Giraffes
и любойIEnumerable<>
из любого подклассаLifeForm
Contravariance (универсальный с параметризованным типом, украшенным
in
)Контравариантность часто используется, когда функции передаются в качестве параметров.
Вот пример функции, которая принимает
Action<Zebra>
в качестве параметра и вызывает ее в известном экземпляре Zebra:Как и ожидалось, это работает просто отлично:
Интуитивно понятно, что это не удастся:
Тем не менее, это удается
и даже это тоже удается
Зачем? Потому
Action
что определяется какAction<in T>
, то естьcontravariant
, это означает, что дляAction<Zebra> myAction
, котороеmyAction
может быть не больше, чем «а»Action<Zebra>
, но менее производные суперклассыZebra
также приемлемы.Хотя вначале это может быть не интуитивно понятно (например, как можно
Action<object>
передать как параметр, требующийAction<Zebra>
?), Если вы распакуете шаги, вы заметите, что вызываемая функция (PerformZebraAction
) сама отвечает за передачу данных (в данном случае этоZebra
экземпляр ) к функции - данные не поступают из вызывающего кода.Из-за перевернутого подхода использования функций более высокого порядка таким образом, к тому времени, когда
Action
вызывается, это более производныйZebra
экземпляр, который вызывается противzebraAction
функции (передаваемой как параметр), хотя сама функция использует менее производный тип.источник
in
ключевое слово для контравариантности ?Action<in T>
иFunc<in T, out TResult>
являются контравариантными в типе ввода. (В моих примерах используются существующие инвариантные (List), ковариантные (IEnumerable) и контравариантные (Action, Func) типы)C#
поэтому не знал бы этого.По сути, всякий раз, когда у вас есть функция, которая принимает Enumerable одного типа, вы не можете передать Enumerable производного типа без явного приведения его.
Просто чтобы предупредить вас о ловушке, хотя:
В любом случае, это ужасный код, но он существует, и изменяющееся поведение в C # 4 может привести к тонким и трудным для поиска ошибкам, если вы используете такую конструкцию.
источник
Из MSDN
источник
контрвариация
В реальном мире вы всегда можете использовать приют для животных вместо приюта для кроликов, потому что каждый раз, когда в приюте для животных размещается кролик, это животное. Однако, если вы используете приют для кроликов вместо приюта для животных, его персонал может быть съеден тигром.
В коде, это означает , что если у вас есть ,
IShelter<Animal> animals
вы можете просто написать ,IShelter<Rabbit> rabbits = animals
если вы обещаете и использоватьT
вIShelter<T>
только как параметры метода следующим образом:и заменить элемент на более общий, то есть уменьшить дисперсию или ввести контрастную дисперсию.
ковариации
В реальном мире вы всегда можете использовать поставщика кроликов вместо поставщика животных, потому что каждый раз, когда поставщик кроликов дает вам кролика, это животное. Однако, если вы используете поставщика животных вместо поставщика кролика, вас может съесть тигр.
В коде, это означает , что если у вас есть ,
ISupply<Rabbit> rabbits
вы можете просто написать ,ISupply<Animal> animals = rabbits
если вы обещаете и использоватьT
вISupply<T>
только в качестве возвращаемого значения метода следующим образом:и заменить элемент более производным, то есть увеличить дисперсию или ввести ко дисперсию.
В общем, это всего лишь проверяемое вами во время компиляции обещание, что вы будете обращаться с универсальным типом определенным образом, чтобы сохранить безопасность типов и никого не съесть.
Возможно, вы захотите прочитать это, чтобы обернуть голову вокруг этого.
источник
contravariance
интересен. Я читаю это как указание на эксплуатационное требование: более общий тип должен поддерживать сценарии использования всех типов, производных от него. Таким образом, в этом случае приют для животных должен быть в состоянии обеспечить укрытие для всех типов животных. В этом случае добавление нового подкласса может нарушить суперкласс! То есть - если мы добавим подтип Tyrannosaurus Rex, то это может разрушить наш существующий приют для животных .Делегат конвертера помогает мне визуализировать обе концепции, работающие вместе:
TOutput
представляет ковариацию, где метод возвращает более конкретный тип .TInput
представляет противоположность, где метод передается менее специфического типа .источник