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();
}