Ковариация и противоположность в языках программирования

StackOverflow https://stackoverflow.com/questions/1163465

Вопрос

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

Это было полезно?

Решение

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

S является подтипом T -iff List [s] является подтипом списка [t

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

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

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

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

Другие советы

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

http://blogs.msdn.com/ericlippert/archive/tags/covariance+ и contravariance/default.aspx

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

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

Существует различие между ковариация а также противоположность.
Очень примерно, операция ковариата, если она сохраняет упорядочение типов, и противоречит, если она реверс этот заказ.

Сам упорядочение предназначено для представления более общих типов как больше, чем более конкретные типы.
Вот один пример ситуации, когда 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);

А 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, поддержка дисперсии в Generics была очищена, и теперь можно использовать новые ключевые слова в а также вне с общими параметрами типа. Они могут определять и ограничивать направление потока данных для конкретного параметра типа, что позволяет дисперсии работать. Но в случае List<T>, данные типа T потоки в обоих направлениях - есть методы на типе List<T> это возвращение T ценности и другие, которые получают такие значения.

Точка этих ограничений на направление Чтобы позволить дисперсию там, где это имеет смысл, но предотвратить проблемы как ошибка времени выполнения, упомянутая в одном из предыдущих примеров массива. Когда параметры типа правильно украшены в или же вне, компилятор может проверить, разрешать или запретить, его дисперсия при Время компиляции. Анкет Microsoft пришла к усилиям по добавлению этих ключевых слов во многие стандартные интерфейсы в .NET Framework, например 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> ковариант из -за вне Спецификатор на параметре типа T.

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

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

Ref:

У Барта де Смета отличная запись в блоге о ковариации и противоположности здесь.

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

Делегировать объект MyCallback (fileStream S);

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

как это:

String somemethod (Stream S);

Здесь тип возврата Somemethod (строка) - это тип, который получен из типа возврата делегата (объект); Эта ковариация разрешена. Тип параметра Somemethod (поток) - это тип, который является базовым классом типа параметра делегата (FileStream); Эта противоположность разрешена.

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

Int32 SomeotherMethod (Stream S);

Несмотря на то, что тип возврата какого -то итога (Int32) получен из типа возврата MyCallback (объект), эта форма ковариации не допускается, поскольку INT32 является типом значения.

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top