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

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

5 ответов


вопрос "какая разница между ковариантность и контравариантность?"

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

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

{ Animal, 
  Tiger, 
  Fruit, 
  Banana }.

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

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

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

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

теперь давайте рассмотрим отношения. Есть совместимость назначение отношения между парами типов в первом сете. Значение типа 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. Ковариация хранит направление передачи. Контравариантность!--23-->реверс его.


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

ковариации

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

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

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

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

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

можно конвертировать из IComparer<object> to IComparer<string> или Action<object> to Action<string>; значения только go на эти объекты.

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

в целом

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

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


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

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

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

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

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

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

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


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

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


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

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

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

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

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