Подтвердить что ты не робот

Почему Action <Action <T>> ковариант?

Это то, с чем я с трудом переворачиваю голову. Я понимаю, что Action<T> является контравариантным и, вероятно, объявлен как таковой.

internal delegate void Action<in T>(T t);

Однако я не понимаю , почему a Action<Action<T>> является ковариантным. T по-прежнему не находится в позиции вывода. Я бы очень признателен, если кто-то попытается объяснить логику/логику.

Я немного потянулся и нашел этот блог, который пытается объяснить это. В частности, я не совсем понял, что здесь подразумевается под подразделом "Объяснение для ковариации ввода".

Это то же самое, если пара "Derived → Base" заменена парой "Action → Action".

4b9b3361

Ответ 1

ОК, поэтому прежде всего давайте понять, что вы подразумеваете, говоря, что Action<Action<T>> является ковариантным. Вы имеете в виду следующее утверждение:

  • Если объекту ссылочного типа X может быть присвоена переменная ссылочного типа Y, то объект типа Action<Action<X>> может быть назначен переменной ссылочного типа Action<Action<Y>>.

Хорошо, посмотрим, работает ли это. Предположим, что мы имеем классы Fish и Animal с очевидным наследованием.

static void DoSomething(Fish fish)
{
    fish.Swim();
}

static void Meta(Action<Fish> action)
{
    action(new Fish());
}

...

Action<Action<Fish>> aaf = Meta;
Action<Fish> af = DoSomething;
aaf(af);

Что это делает? Мы передаем делегата DoSomething в Meta. Это создает новую рыбу, а затем DoSomething заставляет рыбу плыть. Нет проблем.

Пока все хорошо. Теперь вопрос в том, почему это должно быть законным?

Action<Action<Animal>> aaa = aaf;

Хорошо, посмотрим, что произойдет, если мы разрешим это:

aaa(af);

Что происходит? То же, что и раньше, очевидно.

Можем ли мы что-то сделать не так? Что делать, если мы передаем что-то отличное от af до aaa, помня, что это будет передано вместе с Meta.

Хорошо, что мы можем передать на aaa? Любой Action<Animal>:

aaa( (Animal animal) => { animal.Feed(); } );

И что происходит? Мы передаем делегату Meta, который вызывает делегата с новой рыбой, и мы кормим рыбу. Нет проблем.

T все еще не находится в выходной позиции. Я бы очень признателен, если кто-то попытается объяснить логику/логику.

Позиция "позиция ввода/вывода" является мнемонической; ковариантный тип имеет тенденцию иметь T в выходной позиции, а контравариантный тип имеет тенденцию иметь T во входном положении, но это не универсально. В большинстве случаев это правда, поэтому мы выбрали in и out в качестве ключевых слов. Но важно то, что типы могут использоваться только в виде типов.

Вот еще один способ подумать об этом. Ковариация сохраняет направление стрелки. Вы нарисуете стрелку string --> object, вы можете нарисовать "ту же" стрелку IEnumerable<string> --> IEnumerable<object>. Контравариантность меняет направление стрелки. Здесь стрелка X --> Y означает, что ссылка на X может храниться в переменной типа Y:

Fish                         -->     Animal  
Action<Fish>                 <--     Action<Animal> 
Action<Action<Fish>>         -->     Action<Action<Animal>>
Action<Action<Action<Fish>>> <--     Action<Action<Action<Animal>>>
...

Посмотрите, как это работает? Обтекание Action вокруг обеих сторон меняет направление стрелки; что "контравариантный" означает: по мере изменения типов стрелки идут в противоположном направлении. Очевидно, что обратное направление стрелки дважды является тем же самым, что и сохранение направления стрелки.

ДАЛЬНЕЙШИЕ ЧТЕНИЯ:

Мои статьи в блоге, которые я написал при разработке функции. Начните снизу:

http://blogs.msdn.com/b/ericlippert/archive/tags/covariance+and+contravariance/default.aspx

Недавний вопрос о том, как дисперсия определяется как тип typeable компилятором:

Правила отклонения в С#

Ответ 2

Рассмотрим следующий пример:

string s = "Hello World!";
object o = s; // fine

Если мы обернем его с помощью Action

Action<string> s;
Action<object> o;
s = o; // reverse of T polymorphism

Это связано с тем, что эффект параметра in делает иерархию назначаемого типа назначением обратной иерархии параметров типа. Например, если это действительно:

TDerived t1; 
TBase t2; 
t2 = t1; // denote directly assignable without operator overloading

затем

Action<TDerived> at1; 
Action<TBase> at2; 
at1 = at2; // contravariant

. Тогда

Action<Action<TDerived>> aat1;
Action<Action<TBase>> aat2;
aat2 = aat1; // covariant

а также

Action<Action<Action<TDerived>>> aaat1;
Action<Action<Action<TBase>>> aaat2;
aaat1 = aaat2; // contravariant

и т.д.

Эффект и то, как ковариантные и контравариантные работы сравниваются с обычным назначением, объясняются в http://msdn.microsoft.com/en-us/library/dd799517.aspx. Короче говоря, ковариантное присваивание работает как обычный полиморфизм ООП, а контравариант работает назад.

Ответ 3

Рассмотрим это:

class Base { public void dosth(); }
class Derived : Base { public void domore(); }

Использование действия T:

// this is all clear
Action<Base> a1 = x => x.dosth();
Action<Derived> b1 = a1;

Сейчас:

Action<Action<Derived>> a = x => { x(new Derived()); };
Action<Action<Base>> b = a;
// the line above is basically this:
Action<Action<Base>> b = x => { x(new Derived()); };

Что будет работать, потому что вы можете обрабатывать результат new Derived() как Base. Оба класса могут dosth().

Теперь, в этом случае:

Action<Action<Base>> a2 = x => { x(new Derived()); };
Action<Action<Derived>> b2 = x => { x(new Derived()); };

Он все равно будет работать при использовании new Derived(). Однако это нельзя сказать вообще и, следовательно, является незаконным. Рассмотрим это:

Action<Action<Base>> a2 = x => { x(new Base()); };
Action<Action<Derived>> b2 = x => { x(new Base()); };

Ошибка: Action<Action<Derived>> ожидает domore(), но Base выполняет только dosth().

Ответ 4

Существует дерево наследования с объектом в качестве корня. Путь на дереве обычно выглядит примерно так:

object -> Base -> Child

Объект типа, выше на дереве, может быть назначен переменной типа ниже на дереве. Ковариация в общих типах означает, что реализованные типы связаны способом, который следует за деревом

object -> IEnumerable<object> -> IEnumerable<Base> -> IEnumerable<Child>

object -> IEnumerable<object> -> IEnumerable<IEnumerable<object> -> ...

Контравариантность означает, что реализованные типы связаны таким образом, что обращается к дереву.

object -> Action<Child> -> Action<Base> -> Action<object>

Когда вы идете на уровень глубже, вам нужно снова изменить дерево

object -> Action<Action<object>> -> Action<Action<Base>> -> Action<Action<Child>> -> Action<object>

p.s. с контравариантностью иерархия объектов больше не является деревом, а является фактически ориентированным ациклическим графом