Ковариантность и Контравариантность по аргументу одного типа - PullRequest
9 голосов
/ 24 декабря 2010

В спецификации C # говорится, что тип аргумента не может быть одновременно ковариантным и контравариантным.

Это очевидно, когда при создании ковариантного или контравариантного интерфейса вы украшаете параметры вашего типа соответственно "out" или "in". Не существует опции, которая позволяет оба одновременно ("outin").

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

Edit:

Насколько я понимаю, массивы были на самом деле и ковариантными, и контравариантными.

public class Pet{}
public class Cat : Pet{}
public class Siamese : Cat{}
Cat[] cats = new Cat[10];
Pet[] pets = new Pet[10];
Siamese[] siameseCats = new Siamese[10];

//Cat array is covariant
pets = cats; 
//Cat array is also contravariant since it accepts conversions from wider types
cats = siameseCats; 

Ответы [ 7 ]

24 голосов
/ 25 декабря 2010

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

Прежде всего, прочитайте мою статью на тему отклонения «валидность»:

http://blogs.msdn.com/b/ericlippert/archive/2009/12/03/exact-rules-for-variance-validity.aspx

По определению, если тип является "ковариантно допустимым", то он не может использоваться контравариантным образом .Если он «контравариантно действителен», то он не может быть использован ковариантным образом .То, что является одновременно ковариантно действительным и контравариантно действительным, не может использоваться ни ковариантным, ни контравариантным способом .То есть он инвариант .Итак, является объединением ковариантного и контравариантного: их объединение инвариантно .

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

interface IBurger<in and out T> {}

Предположим, у вас есть IBurger<string>.Поскольку оно ковариантно, оно конвертируется в IBurger<object>.Поскольку он является контравариантным, он, в свою очередь, может быть преобразован в IBurger<Exception>, хотя «строка» и «исключение» не имеют ничего общего.По сути, «вход и выход» означает, что IBurger<T1> можно преобразовать в любой тип IBurger<T2> для любых двух ссылочных типов T1 и T2. Чем это полезно? Что бы вы сделали с такой функцией?Предположим, у вас есть IBurger<Exception>, но объект на самом деле IBurger<string>.Что вы могли бы сделать с этим, поскольку оба используют тот факт, что аргумент типа является Исключением, и позволяют этому аргументу типа быть полной ложью, потому что аргумент «реального» типа является совершенно не связанным типом?

Чтобы ответить на ваш следующий вопрос: неявные преобразования типов ссылок с использованием массивов ковариантны ;они не контравариантны.Можете ли вы объяснить, почему вы ошибочно полагаете, что они противоречивы?

8 голосов
/ 24 декабря 2010

Ковариация и контравариантность являются взаимоисключающими. Ваш вопрос похож на вопрос о том, может ли множество A быть одновременно и подмножеством множества B, и подмножеством множества B. Чтобы множество A было одновременно подмножеством и надмножеством множества B, множество A должно быть равно множеству B, поэтому вы бы просто спросили, равно ли множество A множеству B.

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

5 голосов
/ 24 декабря 2010

Ковариация возможна для типов, которые вы никогда не вводите (например, функции-члены могут использовать его в качестве типа возвращаемого значения или параметра out, но никогда не в качестве входного параметра).Контравариантность возможна для типов, которые вы никогда не выводите (например, как входной параметр, но никогда как тип возврата или out параметр).

Если вы сделали параметр типа ковариантным и контравариантным, вы не сможете ввестиэто, и вы не можете вывести его - вы не можете использовать его вообще.

1 голос
/ 24 декабря 2010

Без аргументов без и в ключевых словах - Ковариация и Контравариантность, не так ли?

в означает, что аргумент может использоваться только как тип аргумента функции

out означает, что аргумент может использоваться только как тип возвращаемого значения

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

0 голосов
/ 03 ноября 2018

Параметры общего типа не могут быть одновременно ковариантными и контравариантными.

Почему? Это связано с ограничениями, налагаемыми модификаторами in и out. Если бы мы хотели сделать наш параметр общего типа ковариантным и контравариантным, мы бы в основном сказали:

  • Ни один из методов нашего интерфейса не возвращает T
  • Ни один из методов нашего интерфейса не принимает T

Что, по сути, сделает наш универсальный интерфейс не универсальным.

Я объяснил это подробно под другим вопросом :

0 голосов
/ 19 апреля 2018

Что вы можете сделать с "Ковариант"?

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

Предположим, у вас есть эти класс и интерфейс:

interface ICanOutput<out T> { T getAnInstance(); }

class Outputter<T> : ICanOutput<T>
{
    public T getAnInstance() { return someTInstance; }
}

Теперь предположим, что у вас есть типы TBig, которые наследуют TSmall. Это означает, что экземпляр TBig также всегда является экземпляром TSmall; но экземпляр TSmall не всегда является экземпляром TBig. (Названия были выбраны так, чтобы их было легко визуализировать TSmall вписываясь в TBig)

Когда вы делаете это (классический co вариант назначения):

//a real instance that outputs TBig
Outputter<TBig> bigOutputter = new Outputter<TBig>();

//just a view of bigOutputter
ICanOutput<TSmall> smallOutputter = bigOutputter;
  • bigOutputter.getAnInstance() вернет TBig
  • И поскольку smallOutputter было присвоено bigOutputter:
    • внутренне, smallOutputter.getAnInstance() вернет TBig
    • И TBig можно преобразовать в TSmall
    • преобразование выполнено и вывод TSmall.

Если бы это было наоборот (как если бы это было против вариант):

//a real instance that outputs TSmall
Outputter<TSmall> smallOutputter = new Outputter<TSmall>();

//just a view of smallOutputter
ICanOutput<TBig> bigOutputter = smallOutputter;
  • smallOutputter.getAnInstance() вернется TSmall
  • И поскольку bigOutputter был присвоен smallOutputter:
    • внутренне, bigOutputter.getAnInstance() вернет TSmall
    • Но TSmall нельзя преобразовать в TBig !!
    • Это тогда невозможно.

Именно поэтому типы " contra option" не могут использоваться в качестве выходных types


Что вы можете сделать с "Contravariant"?

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

Предположим, у вас есть эти класс и интерфейс:

interface ICanInput<in T> { bool isInstanceCool(T instance); }

class Analyser<T> : ICanInput<T>
{
    bool isInstanceCool(T instance) { return instance.amICool(); }
}

Снова предположим, что типы TBig наследуют TSmall. Это означает, что TBig может делать все, что делает TSmall (в нем есть все TSmall членов и более). Но TSmall не может делать все, что делает TBig (TBig имеет больше членов).

Когда вы делаете это (классический контраст вариант назначения):

//a real instance that can use TSmall methods
Analyser<TSmall> smallAnalyser = new Analyser<TSmall>();
    //this means that TSmall implements amICool

//just a view of smallAnalyser
ICanInput<TBig> bigAnalyser = smallAnalyser;
  • smallAnalyser.isInstanceCool:
    • smallAnalyser.isInstanceCool(smallInstance) можно использовать методы из smallInstance
    • smallAnalyser.isInstanceCool(bigInstance) также может использовать методы (он смотрит только на TSmall часть TBig)
  • А поскольку bigAnalyser было присвоено smallAnalyer:
    • вполне нормально звонить bigAnalyser.isInstanceCool(bigInstance)

Если бы это было наоборот (как если бы это был co вариант):

//a real instance that can use TBig methods
Analyser<TBig> bigAnalyser = new Analyser<TBig>();
    //this means that TBig has amICool, but not necessarily that TSmall has it    

//just a view of bigAnalyser
ICanInput<TSmall> smallAnalyser = bigAnalyser;
  • Для bigAnalyser.isInstanceCool:
    • bigAnalyser.isInstanceCool(bigInstance) можно использовать методы в bigInstance
    • но bigAnalyser.isInstanceCool(smallInstance) не может найти TBig методов в TSmall !!! И не гарантируется, что этот smallInstance будет даже TBig преобразован.
  • А поскольку smallAnalyser было присвоено bigAnalyser:
    • вызов smallAnalyser.isInstanceCool(smallInstance) попытается найти TBig методы в экземпляре
    • и он может не найти методы TBig, потому что этот smallInstance не может быть экземпляром TBig.

Именно поэтому типы " co вариант" не могут использоваться в качестве входных данных параметры


Объединение обоих

Теперь, что происходит, когда вы добавляете два "cannots" вместе?

  • Не может это + не может = не может ничего

Что вы могли бы сделать?

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

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

  • Один интерфейс, использующий in и имеющий только методы, которые не выводят T
  • Другой интерфейс, использующий out, имеющий только методы, которые не принимают T в качестве ввода

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

0 голосов
/ 25 декабря 2010

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

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

Ковариация означает S <: T ⇒ G<S> <: G<T>, а контравариантность означает S <: T ⇒ G<T> <: G<S>. Должно быть совершенно очевидно, что это никогда не может быть правдой одновременно.

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