Почему ActionActionT ковариант?

17

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

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

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

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

  

Это то же самое, если «Derived - & gt; Base "заменяется на« Action - & gt; Действие ".

    
задан Tejas Sharma 09.05.2013 в 17:48
источник

4 ответа

22

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

  • Если объекту ссылочного типа 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 все еще не находится в выходной позиции. Я бы очень признателен, если кто-то попытается объяснить логику / логику.

Позиция «позиция ввода / вывода» является мнемонической; ковариантный тип tend должен иметь T в выходной позиции и контравариантный тип tend иметь 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 вокруг обеих сторон отменяет направление стрелки; это то, что «контравариант» означает: по мере изменения типов стрелки идут в направлении contra - противоположном направлении. Очевидно, что обратное направление стрелки дважды - это то же самое, что , сохраняющее направление стрелки .

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

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

Ссылка

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

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

    
ответ дан Eric Lippert 09.05.2013 в 18:31
источник
  • Это имеет такой смысл. Спасибо за разъяснение, что материал позиции ввода / вывода не является окончательным способом судить о различии типа. Думаю, я немного смутился. Мне также очень нравится аналогия стрелок. –  Tejas Sharma 09.05.2013 в 18:42
  • @Tejas: Это не аналогия; на самом деле, как ковариация определяется в теории категорий. Теория категорий имеет два основных вида вещей: «объекты» и «стрелки», которые соединяют пары объектов. Ковариантная функция - это функция, которая сохраняет направление стрелки между связанной парой объектов. –  Eric Lippert 09.05.2013 в 19:08
  • хм, я понятия не имел. Ну, я собираюсь почитать ваш блог серии, надеюсь, это должно устранить оставшиеся сомнения / заблуждения, которые я имею о разнице. Еще раз спасибо. –  Tejas Sharma 09.05.2013 в 19:15
3

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

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

Если мы обернем его Action

Action<string> s;
Action<object> o;
s = o; // reverse of T's 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

и т. д.

Эффект и то, как ковариантные и контравариантные работы сравниваются с обычным назначением, объясняются в ссылке . Короче говоря, ковариантное присваивание работает как обычный полиморфизм ООП, а контравариант работает назад.

    
ответ дан tia 09.05.2013 в 18:25
источник
  • Да, я понимаю, что «правило», которое включает тип внутри Action, будет переворачивать дисперсию. Но, как я уже сказал, я не понимаю логику этого правила. –  Tejas Sharma 09.05.2013 в 18:42
  • @Tejas, вы имеете в виду, что не знаете, почему контравариант, как и должен перевернуть дисперсию? –  tia 09.05.2013 в 18:48
  • хорошо, Эрик Липперт неплохо объяснил это, но я был смущен тем, почему каждый раз, когда вы обертываете тип в Action, он переворачивает дисперсию. Вы неплохо объяснили это с чисто математической точки зрения. Я искал более подробное объяснение того, почему это происходит. Спасибо за ваш ответ, хотя! –  Tejas Sharma 09.05.2013 в 18:52
1

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

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() .

    
ответ дан dialer 09.05.2013 в 18:27
источник
1

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

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. с контравариантностью иерархия объектов больше не является деревом, а является фактически ориентированным ациклическим графом

    
ответ дан ILMTitan 09.05.2013 в 19:31
источник