Skip to content

Instantly share code, notes, and snippets.

@eterekhin
Last active June 20, 2020 04:56
Show Gist options
  • Save eterekhin/f82fd2283d6253019dcf496fc4258947 to your computer and use it in GitHub Desktop.
Save eterekhin/f82fd2283d6253019dcf496fc4258947 to your computer and use it in GitHub Desktop.

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();
    }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment