views:

586

answers:

5

I am having trouble understanding the difference between covariance and contravariance.

My understanding is one supports out, as in "action" with no return type, which is casting to. The other takes in an argument and is casting from, is this correct?

+6  A: 

Eric Lippert has a series of posts on his blog about *variance, both from a conceptual perspective and how they relate to C#. Try starting here:

http://blogs.msdn.com/ericlippert/archive/tags/Covariance+and+Contravariance/default.aspx

Adam Robinson
+4  A: 

Read up all of the articles by Eric Lippert on Covariance and Contravariance.

He is one of the writers of the C# compiler and reading through the series of posts above is a great way to understand the differences.

This is the first part.

Oded
IMO, the OP should probably start with the first in the series: http://blogs.msdn.com/ericlippert/archive/2007/10/16/covariance-and-contravariance-in-c-part-one.aspx
Ryan Emerle
@Ryan Emerle - added link to first series entry.
Oded
+5  A: 

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. Usually this sort of thing doesn't come up, fortunately :)

Jon Skeet
An English question: What's the correct preposition to use? For both of them it's "in"? :)
Mehrdad Afshari
In terms of "contravariant in T" and "covariant in T"? I think "in" is fine there; "over" might work too.
Jon Skeet
+23  A: 

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

Covariance and contravariance are properties of mappings between sets.

Consider the following two sets of types:

{ Animal, 
  Tiger, 
  Fruit, 
  Banana }.

And this clearly related set:

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

There is a mapping 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>.

With me so far?

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. Let's write "a value of type X can be assigned to a variable of type Y" in a shorter form: X ⇒ Y. So:

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

In C# 4 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 IE<X> ⇒ IE<Y>.

A mapping which has this property is called a "covariant mapping".

Now consider the set

{ 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>.

Such a mapping is called a contravariant mapping.

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

Eric Lippert
brilliant explanation, after all you made it yourself! :)
this. __curious_geek
One of the .Net 4.0 features i can't wait to play around with. :)
Arnis L.
+1 One of the better explanations of these concepts I've seen.
dkackman