Why does C# use contravariance (not covariance) in input parameters with delegate?

Olivier's answer is correct; I thought I might try to explain this more intuitively.

Why does C# choose to use contravariance (not covariance) in input parameters in delegate?

Because contravariance is typesafe, covariance is not.

Instead of Base, let's say Mammal:

delegate void MammalDelegate(Mammal m);

This means "a function that takes a mammal and returns nothing".

So, suppose we have

void M(Giraffe x)

Can we use that as a mammal delegate? No. A mammal delegate must be able to accept any mammal, but M does not accept cats, it only accepts giraffes.

void N(Animal x)

Can we use that as a mammal delegate? Yes. A mammal delegate must be able to accept any mammal, and N does accept any mammal.

covariance/contravariance rule does not seem to work in this example.

There is no variance here to begin with. You are making the extremely common mistake of confusing assignment compatibility with covariance. Assignment compatibility is not covariance. Covariance is the property that a type system transformation preserves assignment compatibility.

Let me say that again.

You have a method that takes a Mammal. You can pass it a Giraffe. That is not covariance. That is assignment compatibility. The method has a formal parameter of type Mammal. That is a variable. You have a value of type Giraffe. That value can be assigned to that variable, so it is assignment compatible.

What then is variance, if it is not assignment compatibility? Let's look at an example or two:

A giraffe is assignment compatible with a variable of type mammal. Therefore a sequence of giraffes (IEnumerable<Giraffe>) is assignment compatible with a variable of type sequence of mammals (IEnumerable<Mammal>).

That is covariance. Covariance is the fact that we can deduce the assignment compatibility of two types from the assignment compatibility of two other types. We know that a giraffe may be assigned to a variable of type animal; that lets us deduce another assignment compatibility fact about two other types.

Your delegate example:

A mammal is assignment compatible with a variable of type animal. Therefore a method which takes an animal is assignment compatible with a variable of type delegate which takes a mammal.

That is contravariance. Contravariance is again, the fact that we can deduce the assignment compatibility of two things -- in this case a method can be assigned to a variable of a particular type -- from the assignment compatibility of two other types.

The difference between covariance and contravariance is simply that the "direction" is swapped. With covariance we know that A can be used as B implies that I<A> can be used as I<B>. With contravariance we know that I<B> can be used as I<A>.

Again: variance is a fact about the preservation of an assignment compatibility relationship across a transformation of types. It is not the fact that an instance of a subtype may be assigned to a variable of its supertype.

What other cases than delegate use covariance/contravariance and why?

  • Conversion of method groups to delegates uses covariance and contravariance on return and parameter types. This only works when the return / parameter types are reference types.

  • Generic delegates and interfaces may be marked as covariant or contravariant in their type parameters; the compiler will verify that the variance is always typesafe, and if not, will disallow the variance annotation. This only works when the type arguments are reference types.

  • Arrays where the element type is a reference type are covariant; this is not typesafe but it is legal. That is, you may use a Giraffe[] anywhere that an Animal[] is expected, even though you can put a turtle into an array of animals but not into an array of giraffes. Try to avoid doing that.

Note that C# does NOT support virtual function return type covariance. That is, you may not make a base class method virtual Animal M() and then in a derived class override Giraffe M(). C++ allows this, but C# does not.

UPDATE regarding the previous paragraph: This answer was written in 2016; in 2020, C# 9 does now support return type covariance.


Because, if you supply a delegate accepting a less derived input parameter, this method will get a parameter value having a type which is more derived than expected. And this works.

On the other hand, if covariance was used, you could be supplying a delegate expecting a more derived type, but it might get a value of a less derived type. And this does not work.

BaseDelegate b = TakeBBase; // Contravariant. OK.
b(new Base());

Because b is statically declared as BaseDelegate it accepts a value of type Base or a type derived from it. Now, because b is actually calling TakeBBase, it passes this Base value where a BBase value is expected. Since Base is derived from BBase, this is ok.

BaseDelegate b = TakeDerived; // Covariant. DOES NOT COMPILE!
b(new Base());

Now TakeDerived is being called and is getting a value of type Base, but is expecting one of type Derived, which Base is clearly not. Therefore covariance is not type safe.

Note: For output parameters the considerations are exactly the other way around. Therefore out parameters and return values are covariant.

What makes it a bit counterintuitive is the fact that we are not just talking about a value that is more or less derived, but about a delegate accepting (or returning) a value that is more or less derived.

Corresponding arguments apply to generic type parameters. Here you supply more or less derived types having methods, and for those methods (including property getters and setters) it's the same problematic as for your delegates.