Ковариантность и контравариантность в языках программирования - PullRequest
19 голосов
/ 22 июля 2009

Может кто-нибудь объяснить мне, понятие ковариации и контравариантности в теория языков программирования?

Ответы [ 7 ]

18 голосов
/ 22 июля 2009

Ковариантность довольно проста и о ней лучше всего думать с точки зрения некоторого класса коллекции List. Мы можем параметризировать класс List с некоторым параметром типа T. То есть наш список содержит элементы типа T для некоторых T. Список будет ковариантным, если

S является подтипом T тогда и только тогда, когда List [S] является подтипом списка [T]

(где я использую математическое определение тогда для обозначения тогда и только тогда, когда .)

То есть List[Apple] - это List[Fruit]. Если есть какая-то подпрограмма, которая принимает List[Fruit] в качестве параметра, и у меня есть List[Apple], тогда я могу передать это как допустимый параметр.

def something(l: List[Fruit]) {
    l.add(new Pear())
}

Если наш класс коллекции List является изменчивым, то ковариация не имеет смысла, поскольку мы можем предположить, что наша процедура может добавить некоторые другие фрукты (которые не были яблоками), как указано выше. Следовательно, нам нужно только, чтобы неизменные классы коллекций были ковариантными!

17 голосов
/ 22 июля 2009
7 голосов
/ 22 июля 2009

Вот мои статьи о том, как мы добавили новые функции дисперсии в C # 4.0. Начните снизу.

http://blogs.msdn.com/ericlippert/archive/tags/Covariance+and+Contravariance/default.aspx

5 голосов
/ 01 апреля 2010

Для дополнительного удобства, вот упорядоченный список ссылок на все статьи Эрика Липперта об отклонениях:

  1. Ковариация и контравариантность в C #, часть первая
  2. Ковариация и контравариантность в C #, Часть вторая: Ковариация массива
  3. Ковариация и контравариантность в C #, часть третья: дисперсия преобразования группы методов
  4. Ковариация и контравариантность в C #, часть четвертая: дисперсия реального делегата
  5. Ковариантность и контравариантность в C #, часть пятая: функции высшего порядка повреждают мой мозг
  6. Ковариация и Контравариантность в C #, Часть шестая: Разница интерфейса
  7. Ковариантность и контравариантность в C # Часть седьмая: зачем вообще нужен синтаксис?
  8. Ковариация и контравариантность в C #, часть восьмая: параметры синтаксиса
  9. Ковариация и контравариантность в C #, часть девятая: критические изменения
  10. Ковариация и контравариантность в C #, часть десятая: работа с неопределенностью
4 голосов
/ 28 июля 2011

Различают ковариацию и контравариацию .
Грубо говоря, операция является ковариантной, если она сохраняет порядок типов, и контрвариантной, если она переворачивает этот порядок.

Сам порядок должен представлять более общие типы как более крупные, чем более конкретные типы.
Вот один пример ситуации, когда C # поддерживает ковариацию. Во-первых, это массив объектов:

object[] objects=new object[3];
objects[0]=new object();
objects[1]="Just a string";
objects[2]=10;

Конечно, в массив можно вставлять разные значения, потому что в конце они все происходят из System.Object в .Net framework. Другими словами, System.Object является очень общим или большим типом. Теперь вот место, где поддерживается ковариация:
присвоение значения меньшего типа переменной большего типа

string[] strings=new string[] { "one", "two", "three" };
objects=strings;

Переменные объекты типа object[] могут хранить значение, которое на самом деле имеет тип string[].

Подумайте об этом - до некоторой степени, это то, что вы ожидаете, но с другой стороны это не так. В конце концов, в то время как string происходит от object, string[] НЕ происходит от object[]. Языковая поддержка ковариации в этом примере делает возможным в любом случае назначение, что вы найдете во многих случаях. Дисперсия - это функция, которая делает язык более интуитивно понятным.

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

// Runtime exception here - the array is still of type string[],
// ints can't be inserted
objects[2]=10;

// Compiler error here - covariance support in this scenario only
// covers reference types, and int is a value type
int[] ints=new int[] { 1, 2, 3 };
objects=ints;

Пример работы контравариантности немного сложнее. Представь два класса:

public partial class Person: IPerson {
    public Person() {
    }
}

public partial class Woman: Person {
    public Woman() {
    }
}

Woman является производным от Person, очевидно. Теперь представьте, что у вас есть эти две функции:

static void WorkWithPerson(Person person) {
}

static void WorkWithWoman(Woman woman) {
}

Одна из функций что-то делает (неважно, что) с Woman, другая более общая и может работать с любым типом, производным от Person. На стороне Woman у вас теперь есть такие:

delegate void AcceptWomanDelegate(Woman person);

static void DoWork(Woman woman, AcceptWomanDelegate acceptWoman) {
    acceptWoman(woman);
}

DoWork - это функция, которая может принимать Woman и ссылку на функцию, которая также принимает Woman, а затем она передает экземпляр Woman делегату. Рассмотрим полиморфизм элементов, которые у вас есть здесь. Person на больше , чем Woman, а WorkWithPerson на больше , чем WorkWithWoman. WorkWithPerson также считается большим , чем AcceptWomanDelegate для целей дисперсии.

Наконец, у вас есть эти три строки кода:

Woman woman=new Woman();
DoWork(woman, WorkWithWoman);
DoWork(woman, WorkWithPerson);

A Woman экземпляр создан. Затем вызывается DoWork, передавая экземпляр Woman, а также ссылку на метод WorkWithWoman. Последний явно совместим с типом делегата AcceptWomanDelegate - один параметр типа Woman, без возвращаемого типа. Третья строка немного странная. Метод WorkWithPerson принимает Person в качестве параметра, а не Woman, как того требует AcceptWomanDelegate. Тем не менее, WorkWithPerson совместим с типом делегата. Контравариантность делает это возможным, поэтому в случае делегатов больший тип WorkWithPerson может храниться в переменной меньшего типа AcceptWomanDelegate. Еще раз, это интуитивная вещь: , если WorkWithPerson может работать с любым Person, передача Woman не может быть неправильной , верно?

К настоящему времени вам может быть интересно, как все это относится к дженерикам. Ответ в том, что дисперсия может применяться и к генерикам. В предыдущем примере использовались массивы object и string. Здесь код использует общие списки вместо массивов:

List<object> objectList=new List<object>();
List<string> stringList=new List<string>();
objectList=stringList;

Если вы попробуете это, вы увидите, что это не поддерживаемый сценарий в C #. В C # версии 4.0, а также .Net Framework 4.0, поддержка отклонений в обобщениях была очищена, и теперь можно использовать новые ключевые слова в и out с параметрами общего типа. Они могут определять и ограничивать направление потока данных для определенного параметра типа, позволяя работать дисперсии. Но в случае List<T> данные типа T передаются в обоих направлениях - существуют методы типа List<T>, которые возвращают T значения, и другие, которые получают такие значения.

Смысл этих направленных ограничений - , чтобы разрешить дисперсию там, где это имеет смысл , но предотвратить проблемы , такие как ошибка времени выполнения, упомянутая в одном из предыдущих примеров массива. Если параметры типа правильно оформлены с помощью в или из , компилятор может проверить и разрешить или запретить его отклонение в время компиляции , Microsoft приложила усилия для добавления этих ключевых слов во многие стандартные интерфейсы в среде .Net, например IEnumerable<T>:

public interface IEnumerable<out T>: IEnumerable {
    // ...
}

Для этого интерфейса поток данных объектов типа T ясен: они могут быть извлечены только из методов, поддерживаемых этим интерфейсом, а не переданы в них . В результате можно построить пример, аналогичный описанной ранее попытке List<T>, но с использованием IEnumerable<T>:

IEnumerable<object> objectSequence=new List<object>();
IEnumerable<string> stringSequence=new List<string>();
objectSequence=stringSequence;

Этот код приемлем для компилятора C # начиная с версии 4.0, потому что IEnumerable<T> является ковариантным из-за спецификатора out для параметра типа T.

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

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

Ссылка:

0 голосов
/ 24 октября 2012

Как C #, так и CLR допускают ковариацию и контр-дисперсию ссылочных типов, когда привязка метода к делегату. Ковариантность означает, что метод может возвращать тип, который происходит от типа возврата делегата. Контрастность означает, что метод может принимать параметр, который является основой типа параметра делегата. Например, дан делегат определяется так:

делегировать объект MyCallback (FileStream s);

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

как это:

String SomeMethod (Stream s);

Здесь возвращаемый тип SomeMethod (String) - это тип, полученный из возвращаемого типа делегата (Object); эта ковариация разрешена. Тип параметра SomeMethod (Stream) - это тип, который является базовым классом типа параметра делегата (FileStream); это противоречие разрешено.

Обратите внимание, что ковариация и контр-дисперсия поддерживаются только для ссылочных типов, а не для типов значений или для void. Так, например, я не могу привязать следующий метод к делегату MyCallback:

Int32 SomeOtherMethod (Stream s);

Несмотря на то, что тип возвращаемого SomeOtherMethod (Int32) является производным от возврата MyCallback type (Object), эта форма ковариации не допускается, поскольку Int32 является типом значения.

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

0 голосов
/ 11 сентября 2009

Барт Де Смет имеет отличную запись в блоге о ковариации и контравариантности здесь .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...