Что вы можете сделать с "Ковариант"?
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
в качестве ввода
Используйте каждый интерфейс в нужной ситуации, но не пытайтесь назначить один другому.