Разница между Ковариацией и контра-дисперсией


У меня возникли проблемы с пониманием разницы между ковариантности и контрвариантности.

5 134

5 ответов:

ковариантность и контравариантность свойства функция отображения, которая связывает один элемент набора с другим. Более конкретно, отображение может быть ковариантным или контравариантным относительно a отношения на этом наборе.

рассмотрим следующие два подмножества набора всех типов C#. Первый:

{ Animal, 
  Tiger, 
  Fruit, 
  Banana }.

и второй, этот явно связанный набор:

{ IEnumerable<Animal>, 
  IEnumerable<Tiger>, 
  IEnumerable<Fruit>, 
  IEnumerable<Banana> }

есть картография операция от первого набора ко второму набору. То есть, для каждого T в первом сете тегом введите во втором наборе IEnumerable<T>. Или, короче говоря, отображение T → IE<T>. Обратите внимание, что это "тонкая стрела".

со мной до сих пор?

теперь давайте рассмотрим a отношения. Существует совместимость назначение отношения между парами типов в первом сете. Значение типа Tiger может быть присвоено переменной типа Animal, поэтому эти типы называются "совместимыми с назначением". Давайте напишем "значение типа X может быть присвоено переменной типа Y" в сокращенной форме: X ⇒ Y. Обратите внимание, что это "жирная стрела".

Итак, в нашем первом подмножестве, вот все отношения совместимости назначения:

Tiger  ⇒ Tiger
Tiger  ⇒ Animal
Animal ⇒ Animal
Banana ⇒ Banana
Banana ⇒ Fruit
Fruit  ⇒ Fruit

в C# 4, который поддерживает ковариантная совместимость назначения определенных интерфейсов, существует отношение совместимости назначения между парами типов во втором наборе:

IE<Tiger>  ⇒ IE<Tiger>
IE<Tiger>  ⇒ IE<Animal>
IE<Animal> ⇒ IE<Animal>
IE<Banana> ⇒ IE<Banana>
IE<Banana> ⇒ IE<Fruit>
IE<Fruit>  ⇒ IE<Fruit>

обратите внимание, что отображение T → IE<T>сохраняет существование и направление совместимости задание. То есть, если X ⇒ Y, тогда это тоже правда, что IE<X> ⇒ IE<Y>.

если у нас есть две вещи по обе стороны от жирной стрелки, то мы можем заменить обе стороны с чем-то на правой руке сторона соответствующей тонкой стрелки.

отображение, которое имеет это свойство по отношению к определенному отношению, называется "ковариантным отображением". Это должно иметь смысл: последовательность тигров может использоваться там, где необходима последовательность животных, но обратное неверно. Последовательность животных не обязательно должна использоваться там, где необходима последовательность тигров.

это ковариация. Теперь рассмотрим это подмножество множества всех типа:

{ IComparable<Tiger>, 
  IComparable<Animal>, 
  IComparable<Fruit>, 
  IComparable<Banana> }

теперь мы есть отображение из первого набора в третий набор T → IC<T>.

В C# 4:

IC<Tiger>  ⇒ IC<Tiger>
IC<Animal> ⇒ IC<Tiger>     Backwards!
IC<Animal> ⇒ IC<Animal>
IC<Banana> ⇒ IC<Banana>
IC<Fruit>  ⇒ IC<Banana>     Backwards!
IC<Fruit>  ⇒ IC<Fruit>

то есть, отображение T → IC<T> и сохранил существование, но изменил направление совместимость назначение. То есть, если X ⇒ Y, потом IC<X> ⇐ IC<Y>.

отображение, которое сохраняет, но меняет отношение называется контравариантным сопоставление.

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

так вот в чем разница между ковариацией и контравариантностью в C# 4. Ковариация хранит направление передачи. Контравариантность реверс его.

наверное, проще всего привести примеры-именно так я их и запомнил.

ковариации

канонические примеры: IEnumerable<out T>,Func<out T>

вы можете конвертировать из IEnumerable<string> до IEnumerable<object> или Func<string> до Func<object>. Значения только приходят С эти объекты.

это работает, потому что если вы только принимаете значения из API, и он собирается вернуть что-то конкретное (например,string), вы можете лечить это возвращаемое значение как более общий тип (например object).

контравариантность

канонические примеры: IComparer<in T>,Action<in T>

вы можете конвертировать из IComparer<object> до IComparer<string> или Action<object> до Action<string>; ценности идут только на эти объекты.

на этот раз это работает, потому что если API ожидает чего-то общего (например object) вы можете дать ему что-то более конкретное (например, string).

в целом

если у вас есть интерфейс IFoo<T> он может быть ковариантным в T (т. е. объявить его как IFoo<out T> если T используется только в выходной позиции (например, тип возврата) в интерфейсе. Он может быть контравариантным в T (т. е. IFoo<in T>) если T используется только в позиции ввода (например, типа параметра).

это становится потенциально запутанным, потому что" выходная позиция " не так проста, как звучит-параметр типа Action<T> по-прежнему только с помощью T в выходном положении-контравариантность Action<T> поворачивает его, если вы понимаете, что я имею в виду. Это "выход" в том, что значения могут передаваться из реализации метода в направлении код вызывающего абонента, как и возвращаемое значение может. Обычно такого рода вещи не придумывают, к счастью:)

Я надеюсь, что мой пост поможет получить язык-агностический взгляд на эту тему.

что означает "согласованность"? Идея заключается в разработке типобезопасных иерархий типов с сильно заменяемыми типами. Ключ для получения этой согласованности-соответствие на основе подтипа, если вы работаете на статически типизированном языке. (Мы обсудим Принцип подстановки Лисков () на высоком уровне.)

практические примеры (псевдокод / недопустимый в C#):

  • ковариация: предположим, что птицы откладывают яйца "последовательно" со статическим типом: если тип птица откладывает яйцо, не будет ли подтип птицы откладывать подтип яйца? Например, тип утка закладывает утку, затем дается консистенция. Почему это последовательно? Потому что в таком выражении:Egg anEgg = aBird.Lay();ссылка aBird может быть юридически заменена птица или утка экземпляр. Мы говорим, что возвращаемый тип ковариантен типу, в котором определен Lay (). Переопределение подтипа может возвращать более специализированный тип. => "Они доставляют больше."

  • контравариантность: предположим, что пианисты могут играть "последовательно" со статической типизацией: если пианист играет на пианино, сможет ли она играть GrandPiano? Не лучше ли виртуозу сыграть дедушку? (Будьте осторожны, есть поворот!) Это непоследовательно! Потому что в таком выражение: aPiano.Play(aPianist); aPiano не может быть легально заменен фортепиано или экземпляром GrandPiano! В GrandPiano может воспроизводиться только виртуоз, пианисты тоже общие! GrandPianos должны воспроизводиться более общими типами, тогда игра является последовательной. Мы говорим, что тип параметра является контравариантным типу, в котором определяется Play (). Переопределение подтипа может принимать более обобщенный тип. => "Они требуют меньше."

вернуться к C#:
Потому что C# в основном статически типизированный язык," местоположения " интерфейса типа, которые должны быть со - или контравариантными (например, параметры и возвращаемые типы), должны быть явно отмечены, чтобы гарантировать последовательное использование/развитие этого типа, чтобы сделать работу LSP прекрасной. В динамически типизированных языках согласованность LSP обычно не является проблемой, другими словами, вы можете полностью избавиться от совместной и контравариантной "разметки" на интерфейсах и делегатах .Net, если вы используете только тип dynamic в своих типах. - Но это это не лучшее решение в C# (вы не должны использовать динамические в открытых интерфейсах).

вернемся к теории:
Описанное соответствие (ковариантные возвращаемые типы / контравариантные типы параметров) является теоретическим идеалом (поддерживается языками Emerald и POOL-1). Некоторые языки ООП (например, Eiffel) решили применить другой тип согласованности, esp. также ковариантны типы параметров, поскольку они лучше описывают реальность, чем теоретический идеал. В статически типизированных языках желаемая согласованность часто должна достигаться путем применения шаблонов проектирования, таких как" двойная отправка "и"посетитель". Другие языки предоставляют так называемую "множественную отправку" или несколько методов (это в основном выбор перегрузок функции при времени, например, с CLOS) или получить желаемый эффект с помощью динамического ввода.

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

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

делегат конвертера помогает мне понять разницу.

delegate TOutput Converter<in TInput, out TOutput>(TInput input);

TOutput представляет ковариации где метод возвращает a более конкретную типа.

TInput представляет контравариантность где метод передается a меньше конкретного типа.

public class Dog { public string Name { get; set; } }
public class Poodle : Dog { public void DoBackflip(){ System.Console.WriteLine("2nd smartest breed - woof!"); } }

public static Poodle ConvertDogToPoodle(Dog dog)
{
    return new Poodle() { Name = dog.Name };
}

List<Dog> dogs = new List<Dog>() { new Dog { Name = "Truffles" }, new Dog { Name = "Fuzzball" } };
List<Poodle> poodles = dogs.ConvertAll(new Converter<Dog, Poodle>(ConvertDogToPoodle));
poodles[0].DoBackflip();