Разница между ковариацией и повышающим приведением

В чем разница между ковариацией и преобразованием вверх или, точнее, почему им даются разные имена?

Я видел следующий пример, называемый «апкастированием»:

string s = "hello";
object o = s;  //upcast to 'string' to 'object'

Принимая во внимание, что следующее, что я видел, называется «ковариация»:

string[] s = new string[100];
object[] o = s;

IEnumerable<string> ies = new List<string>();
IEnumerable<object> ieo = ies;

Теперь, на мой неопытный взгляд, ковариация кажется такой же, как приведение вверх, за исключением того, что она относится к приведению коллекций. (И аналогичное утверждение можно сделать относительно контравариантности и принижения).

Это действительно так просто?


person James Wiseman    schedule 15.07.2011    source источник
comment
Ответ Джейсона в значительной степени точен. Более подробное объяснение разницы между ковариацией и совместимостью присваиваний см. в моей статье на эту тему. blogs.msdn.com/b/ericlippert/archive/2009/11/30/ Вы не одиноки; многие люди путают эти два понятия.   -  person Eric Lippert    schedule 15.07.2011
comment
@ Эрик Липперт: Ура. Если бы я прочитал эту статью раньше, я, возможно, никогда бы не опубликовал это! Я рад, что сделал это. По правде говоря, я был удивлен, что на SO еще не было подобного вопроса.   -  person James Wiseman    schedule 15.07.2011


Ответы (6)


Теперь, на мой неопытный взгляд, ковариация кажется такой же, как приведение вверх, за исключением того, что она относится к приведению коллекций. (И аналогичное утверждение можно сделать относительно контравариантности и принижения).

Это действительно так просто?

Ковариация не связана с повышением класса, хотя я понимаю, почему вы думаете, что это связано.

Ковариация связана со следующей очень простой идеей. Допустим, у вас есть переменная derivedSequence типа IEnumerable<Derived>. Допустим, у вас есть переменная baseSequence типа IEnumerable<Base>. Здесь Derived происходит от Base. Затем, с ковариацией, следующее является допустимым присвоением, и происходит неявное преобразование ссылки:

baseSequence = derivedSequence;

Обратите внимание, что это не повышение. Это не тот случай, когда IEnumerable<Derived> происходит от IEnumerable<Base>. Скорее, именно ковариация позволяет присвоить значение переменной derivedSequence переменной baseSequence. Идея состоит в том, что переменным типа Base можно присваивать объекты типа Derived, а поскольку IEnumerable<T> является ковариантным в своем параметре, объекты типа IEnumerable<Derived> можно присваивать переменным типа IEnumerable<Base>.

Конечно, я еще толком не объяснил, что такое ковариация. В общем, ковариация связана со следующей простой идеей. Допустим, у вас есть отображение F из типов в типы (я буду обозначать это отображение F<T>; для данного типа T его изображением при отображении F будет F<T>.) Предположим, что это отображение обладает следующим особым свойством:

если X является назначением, совместимым с Y, то F<X> также является назначением, совместимым с F<Y>.

В этом случае мы говорим, что F является ковариантным по своему параметру T. (Здесь сказать, что A является присваиванием, совместимым с B, где A и B являются ссылочными типами, означает, что экземпляры B могут храниться в переменных типа A.)

В нашем случае IEnumerable<T> в C# 4.0 — неявное преобразование ссылок из экземпляров IEnumerable<Derived> в IEnumerable<Base>, если Derived является производным от Base. Направление совместимости присваивания сохраняется, и поэтому мы говорим, что IEnumerable<T> является ковариантным в своем параметре типа.

person jason    schedule 15.07.2011
comment
Пока это единственный ответ, который действительно объясняет разницу, так хорошо сделанную. - person Eric Lippert; 15.07.2011
comment
Да, замечательный ответ, заслуживающий как минимум столько же голосов, сколько сейчас имеет вопрос. Отсюда согласие, которое я сейчас исполню. - person James Wiseman; 15.07.2011
comment
... Это ни в коем случае не принижает другие ответы, конечно :-) - person James Wiseman; 15.07.2011

Приведение означает изменение статического типа объектов и выражений.

Вариантность относится к взаимозаменяемости или эквивалентности типов в определенных ситуациях (например, параметров, универсальных типов и возвращаемых типов).

person Jon    schedule 15.07.2011

IEnumerable<string> не является производным от IEnumerable<object>, поэтому преобразование между ними не выполняется. IEnumerable является ковариантным в своем параметре типа, а строка является производной от объекта, поэтому приведение разрешено.

person antlersoft    schedule 15.07.2011
comment
Поскольку это такой популярный вопрос, я собирался расширить этот ответ, но я сошлюсь на очень хороший ответ @Jason. - person antlersoft; 15.07.2011

Причина, по которой они являются разными концепциями, заключается в том, что, в отличие от восходящего приведения, ковариация не всегда допускается. Разработчикам системы типов было бы легко сделать так, чтобы IList<Cat> считалось «производным» от IList<Animal>, но тогда мы сталкиваемся с проблемами:

IList<Cat> cats = new List<Cat>();
IList<Animal> animals = cats; 
animals.Add(new Dog()); //Uh oh!

Если бы это было разрешено, теперь наш список cats содержал бы Dog!

Напротив, в интерфейсе IEnumerable<T> нет возможности добавлять элементы, так что это вполне допустимо (в C# 4.0):

IList<Cat> cats = new List<Cat>();
IEnumerable<Animal> animals = cats;
//There's no way to add things to an IEnumerable<Animal>, so here we are ok
person BlueRaja - Danny Pflughoeft    schedule 15.07.2011

Сообщение в блоге ниже имеет хорошее объяснение этого:

http://blogs.msdn.com/b/csharpfaq/archive/2010/02/16/covariance-and-contravariance-faq.aspx

person Jon Egerton    schedule 15.07.2011

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

person ComeIn    schedule 04.09.2014