Difference between Covariance & Contra-variance

The question is "what is the difference between covariance and contravariance?"

Covariance and contravariance are properties of a mapping function that associates one member of a set with another. More specifically, a mapping can be covariant or contravariant with respect to a relation on that set.

Consider the following two subsets of the set of all C# types. First:

{ Animal, 
  Tiger, 
  Fruit, 
  Banana }.

And second, this clearly related set:

{ IEnumerable<Animal>, 
  IEnumerable<Tiger>, 
  IEnumerable<Fruit>, 
  IEnumerable<Banana> }

There is a mapping operation from the first set to the second set. That is, for each T in the first set, the corresponding type in the second set is IEnumerable<T>. Or, in short form, the mapping is T → IE<T>. Notice that this is a "thin arrow".

With me so far?

Now let's consider a relation. There is an assignment compatibility relationship between pairs of types in the first set. A value of type Tiger can be assigned to a variable of type Animal, so these types are said to be "assignment compatible". Let's write "a value of type X can be assigned to a variable of type Y" in a shorter form: X ⇒ Y. Notice that this is a "fat arrow".

So in our first subset, here are all the assignment compatibility relationships:

Tiger  ⇒ Tiger
Tiger  ⇒ Animal
Animal ⇒ Animal
Banana ⇒ Banana
Banana ⇒ Fruit
Fruit  ⇒ Fruit

In C# 4, which supports covariant assignment compatibility of certain interfaces, there is an assignment compatibility relationship between pairs of types in the second set:

IE<Tiger>  ⇒ IE<Tiger>
IE<Tiger>  ⇒ IE<Animal>
IE<Animal> ⇒ IE<Animal>
IE<Banana> ⇒ IE<Banana>
IE<Banana> ⇒ IE<Fruit>
IE<Fruit>  ⇒ IE<Fruit>

Notice that the mapping T → IE<T> preserves the existence and direction of assignment compatibility. That is, if X ⇒ Y, then it is also true that IE<X> ⇒ IE<Y>.

If we have two things on either side of a fat arrow, then we can replace both sides with something on the right hand side of a corresponding thin arrow.

A mapping which has this property with respect to a particular relation is called a "covariant mapping". This should make sense: a sequence of Tigers can be used where a sequence of Animals is needed, but the opposite is not true. A sequence of animals cannot necessarily be used where a sequence of Tigers is needed.

That's covariance. Now consider this subset of the set of all types:

{ IComparable<Tiger>, 
  IComparable<Animal>, 
  IComparable<Fruit>, 
  IComparable<Banana> }

now we have the mapping from the first set to the third set T → IC<T>.

In C# 4:

IC<Tiger>  ⇒ IC<Tiger>
IC<Animal> ⇒ IC<Tiger>     Backwards!
IC<Animal> ⇒ IC<Animal>
IC<Banana> ⇒ IC<Banana>
IC<Fruit>  ⇒ IC<Banana>     Backwards!
IC<Fruit>  ⇒ IC<Fruit>

That is, the mapping T → IC<T> has preserved the existence but reversed the direction of assignment compatibility. That is, if X ⇒ Y, then IC<X> ⇐ IC<Y>.

A mapping which preserves but reverses a relation is called a contravariant mapping.

Again, this should be clearly correct. A device which can compare two Animals can also compare two Tigers, but a device which can compare two Tigers cannot necessarily compare any two Animals.

So that's the difference between covariance and contravariance in C# 4. Covariance preserves the direction of assignability. Contravariance reverses it.


It's probably easiest to give examples - that's certainly how I remember them.

Covariance

Canonical examples: IEnumerable<out T>, Func<out T>

You can convert from IEnumerable<string> to IEnumerable<object>, or Func<string> to Func<object>. Values only come out from these objects.

It works because if you're only taking values out of the API, and it's going to return something specific (like string), you can treat that returned value as a more general type (like object).

Contravariance

Canonical examples: IComparer<in T>, Action<in T>

You can convert from IComparer<object> to IComparer<string>, or Action<object> to Action<string>; values only go into these objects.

This time it works because if the API is expecting something general (like object) you can give it something more specific (like string).

More generally

If you have an interface IFoo<T> it can be covariant in T (i.e. declare it as IFoo<out T> if T is only used in an output position (e.g. a return type) within the interface. It can be contravariant in T (i.e. IFoo<in T>) if T is only used in an input position (e.g. a parameter type).

It gets potentially confusing because "output position" isn't quite as simple as it sounds - a parameter of type Action<T> is still only using T in an output position - the contravariance of Action<T> turns it round, if you see what I mean. It's an "output" in that the values can pass from the implementation of the method towards the caller's code, just like a return value can. Usually this sort of thing doesn't come up, fortunately :)