Что можно сделать с «Ковариантом»?
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
- And because
smallOutputter
was assigned with bigOutputter
:
- internally,
smallOutputter.getAnInstance()
will return 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
- And because
bigOutputter
was assigned with smallOutputter
:
- internally,
bigOutputter.getAnInstance()
will return TSmall
- Но
TSmall
нельзя преобразовать в TBig
!!
- Тогда это невозможно.
Вот почему типы «против вариантов» не могут использоваться в качестве выходных типов.
Что можно сделать с "Контравариантом"?
Следуя той же идее, описанной выше, контравариант использует модификатор 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)
can use the methods in smallInstance
smallAnalyser.isInstanceCool(bigInstance)
также может использовать методы (он смотрит только на TSmall
часть TBig
)
- And since
bigAnalyser
was assigned with smallAnalyer
:
- it's totally ok to call
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;
- For
bigAnalyser.isInstanceCool
:
bigAnalyser.isInstanceCool(bigInstance)
can use the methods in bigInstance
- но
bigAnalyser.isInstanceCool(smallInstance)
не может найти TBig
методов в _56 _ !!! И не гарантируется, что этот smallInstance
даже TBig
преобразованный.
- And since
smallAnalyser
was assigned with bigAnalyser
:
- calling
smallAnalyser.isInstanceCool(smallInstance)
will try to find TBig
methods in the instance
- и он может не найти
TBig
методы, потому что этот smallInstance
не может быть экземпляром TBig
.
Вот почему типы «co variant» не могут использоваться в качестве входных параметров.
Присоединение к обоим
Что же произойдет, если сложить две «канноты»?
- Не может это + не может это = ничего не может
Что вы могли сделать?
Я не тестировал это (пока ... я думаю, будет ли у меня причина для этого), но, похоже, все в порядке, если вы знаете, что у вас будут некоторые ограничения.
Если у вас есть четкое разделение методов, которые выводят только желаемый тип, и методов, которые принимают его только как входной параметр, вы можете реализовать свой класс с двумя интерфейсами.
- Один интерфейс, использующий
in
и имеющий только методы, которые не выводят T
- Другой интерфейс, использующий
out
только методы, которые не принимают T
в качестве входных данных
Используйте каждый интерфейс в требуемой ситуации, но не пытайтесь назначить один другому.
person
Daniel Möller
schedule
19.04.2018