Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is Action<Action<T>> covariant?

This is something I'm having a hard time wrapping my head around. I understand that Action<T> is contravariant and is probably declared as such.

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

However, I don't understand why an Action<Action<T>> is covariant. T is still not in an output position. I'd really appreciate it if someone could try to explain the reasoning / logic behind this.

I dug around a little bit and found this blog post which tries to explain it. In particular, I didn't quite follow what was meant here under the "Explanation for covariance of input" subsection.

It is the same natural if the “Derived -> Base” pair is replaced by “Action -> Action” pair.

like image 350
Tejas Sharma Avatar asked May 09 '13 15:05

Tejas Sharma


People also ask

What does covariant mean in programming?

In object-oriented programming, a covariant return type of a method is one that can be replaced by a "narrower" type when the method is overridden in a subclass. A notable language in which this is a fairly common paradigm is C++. C# supports return type covariance as of version 9.0.

What is difference between covariance and Contravariance?

In C#, covariance and contravariance enable implicit reference conversion for array types, delegate types, and generic type arguments. Covariance preserves assignment compatibility and contravariance reverses it.

What is covariance and Contravariance in Java?

Covariance can be translated as "different in the same direction," or with-different, whereas contravariance means "different in the opposite direction," or against-different. Covariant and contravariant types are not the same, but there is a correlation between them. The names imply the direction of the correlation.

What is type variance?

In short, type variance describes the types that may be substituted in place of another type. Covariance: We say a substitution is covariant with a type, Foo, if Foo or any other class with a subclass relationship to Foo is valid.


1 Answers

OK, so first of all let's be clear what you mean by saying that Action<Action<T>> is covariant. You mean that the following statement holds:

  • If an object of reference type X may be assigned to a variable of reference type Y, then an object of type Action<Action<X>> may be assigned to a variable of reference type Action<Action<Y>>.

Well, let's see if that works. Suppose we have classes Fish and Animal with the obvious inheritance.

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

What does that do? We pass a delegate to DoSomething to Meta. That creates a new fish, and then DoSomething makes the fish swim. No problem.

So far so good. Now the question is, why should this be legal?

Action<Action<Animal>> aaa = aaf;

Well, let's see what happens if we allow it:

aaa(af);

What happens? Same thing as before, obviously.

Can we make something go wrong here? What if we pass something other than af to aaa, remembering that doing so will pass it along to Meta.

Well, what can we pass to aaa? Any Action<Animal>:

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

And what happens? We pass the delegate to Meta, which invokes the delegate with a new fish, and we feed the fish. No problem.

T is still not in an output position. I'd really appreciate it if someone could try to explain the reasoning / logic behind this.

The "input/output" position thing is a mnemonic; covariant type tend to have the T in the output position and contravariant type tend to have the T in the input position, but that is not universally true. For the majority of cases, that's true, which is why we chose in and out as the keywords. But what really matters is that the types can only be used in a typesafe manner.

Here's another way to think about it. Covariance preserves the direction of an arrow. You draw an arrow string --> object, you can draw the "same" arrow IEnumerable<string> --> IEnumerable<object>. Contravariance reverses the direction of an arrow. Here the arrow is X --> Y means that a reference to X may be stored in a variable of type Y:

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

See how that works? Wrapping Action around both sides reverses the direction of the arrow; that's what "contravariant" means: as the types vary, the arrows go in the contra -- opposing -- direction. Obviously reversing the direction of an arrow twice is the same thing as preserving the direction of an arrow.

FURTHER READING:

My blog articles that I wrote while designing the feature. Start from the bottom:

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

A recent question about how variance is determined to be typesafe by the compiler:

Variance rules in C#

like image 130
Eric Lippert Avatar answered Sep 19 '22 22:09

Eric Lippert