In - контравариантность, такое обозначение используется, для приведения вверх по цепочке наследования generic'a
Out - контравариантность, такое обозначение используется, для приведения вниз по цепочке наследования generic'a
public interface TestOut<out T>
{
}
interface IWriteControllerIn<in T, out T1>
{
void SaveMany(TestOut<T> test);
TestOut<T1> SaveMany();
}
При ситуации выше (параметр T IWriteControllerIn
контравариантный) TestOut
должен обязательно иметь ковариантный параметр T,
это логично, ведь при таком касте:
IWriteControllerIn<Derived, Base> derivedBase = default(IWriteControllerIn<Base, Base>);
Метод SaveMany
принимает TestOut<Derived>
, а не TestOut<Base>
, как было до приведения, но так как TestOut<>
ковариантный, то нам удастся привести TestOut<Derived>
к TestOut<Base>
и можно считать, что в метод приходит тип TestOut<Base>
.
А теперь пусть T имеет модификатор out, а не in. Что тогда будет?
Вот версия, которая будет компилироваться:
public interface TestOut<in T>
{
}
interface IWriteControllerIn<out T>
{
void SaveMany(TestOut<T> test);
}
В данном случае модификатор у TestOut<>
будет in, это можно пояснить так:
Когда мы приведем IWriteControllerIn<Derived>
к IWriteControllerIn<Base>
, то в метод SaveMany
будет приходить уже не TestOut<Derived>
, а TestOut<Base>
и при этом поскольку TestOut
контравариантный, то TestOut<Base>
сможет неявно привестись к TestOut<Derived>
IWriteControllerIn<Base> controller = default(IWriteController<Derived>);
//// вот таким приведением, можно снова вернуть первоначальную сигнатуру.
((IWriteControllerIn<Derived>)controller).SaveMany((TestOut<Derived>)default(TestOut<Base>));
Однако если использовать TestOut<>
как возвращаемое значение, то модификатор нужно будет поменять на **out **
public interface TestOut<out T>
{
}
interface IWriteControllerIn<out T>
{
TestOut<T> SaveMany();
}
В доказательство корректности можно заметить, когда мы приведем IWriteControllerIn<Derived>
к IWriteControllerIn<Base>
, то возвращаемое значение метода SaveMany() сменится с TestOut<Derived>
на TestOut<Base>
, и это валидно, так как TestOut
ковариантен:
TestOut<Base> saveMany = default(IWriteControllerIn<Derived>).SaveMany(); // валидно, то же самое что :
saveMany = default(IWriteControllerIn<Base>).SaveMany();
То же самое утверждение можно сделать если заменить параметры out на in:
public interface TestOut<in T>
{
}
interface IWriteControllerIn<in T>
{
TestOut<T> SaveMany();
}
TestOut<Derived> saveMany = default(IWriteControllerIn<Derived>).SaveMany(); // валидно, то же самое что :
saveMany = default(IWriteControllerIn<Base>).SaveMany();
Таким образом можно сформулировать несколько утверждений:
-
Если generic класс имеет модификатор in у параметра T и имеет метод, принимающий еще один generic тип, заполняется тем же параметром T, то этот generic тип из параметра метода должен иметь модификатор out
-
Если generic класс имеет модификатор out у параметра T и имеет метод, принимающий еще один generic тип, заполняется тем же параметром T, то этот generic тип из параметра метода должен иметь модификатор in
-
Если generic класс имеет модификатор in у параметра T и имеет метод, возвращающий еще один generic тип, заполняется тем же параметром T, то этот generic тип из возвращаемого значения должен иметь модификатор in
-
Если generic класс имеет модификатор out у параметра T и имеет метод, возвращающий еще один generic тип, заполняется тем же параметром T, то этот generic тип из возвращаемого значения должен иметь модификатор out
Эти утверждения можно продемонстрировать так:
//<-----------------------------Claim 1-------------------------------->
interface IClaim1TestOut<in T>
{
}
interface IClaim1Interface<out T>
{
void SaveMany(IClaim1TestOut<T> item);
}
//<-----------------------------Claim 2-------------------------------->
interface IClaim2TestOut<out T>
{
}
interface IClaim2Interface<in T>
{
void SaveMany(IClaim2TestOut<T> item);
}
//<-----------------------------Claim 3-------------------------------->
interface IClaim3TestOut<out T>
{
}
interface IClaim3Interface<out T>
{
IClaim3TestOut<T> SaveMany();
}
//<-----------------------------Claim 4-------------------------------->
interface IClaim4TestOut<in T>
{
}
interface IClaim4Interface<in T>
{
IClaim4TestOut<T> SaveMany();
}