Difference between Covariance & Contra-variance

Viewed 34549

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

6 Answers

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

The converter delegate helps me to understand the difference.

delegate TOutput Converter<in TInput, out TOutput>(TInput input);

TOutput represents covariance where a method returns a more specific type.

TInput represents contravariance where a method is passed a less specific type.

public class Dog { public string Name { get; set; } }
public class Poodle : Dog { public void DoBackflip(){ System.Console.WriteLine("2nd smartest breed - woof!"); } }

public static Poodle ConvertDogToPoodle(Dog dog)
{
    return new Poodle() { Name = dog.Name };
}

List<Dog> dogs = new List<Dog>() { new Dog { Name = "Truffles" }, new Dog { Name = "Fuzzball" } };
List<Poodle> poodles = dogs.ConvertAll(new Converter<Dog, Poodle>(ConvertDogToPoodle));
poodles[0].DoBackflip();

Co and Contra variance are pretty logical things. Language type system forces us to support real life logic. It's easy to understand by example.

Covariance

For instance you want to buy a flower and you have two flowers shop in your city: rose shop and daisy shop.

If you ask someone "where is the flowers shop?" and someone tells you where is rose shop, would it be okay? Yes, because rose is a flower, if you want to buy a flower you can buy a rose. The same applies if someone replied you with the address of the daisy shop.

This is example of covariance: you are allowed to cast A<C> to A<B>, where C is a subclass of B, if A produces generic values (returns as a result from the function). Covariance is about producers, that's why C# use keyword out for covariance.

Types:

class Flower {  }
class Rose: Flower { }
class Daisy: Flower { }

interface FlowerShop<out T> where T: Flower {
    T getFlower();
}

class RoseShop: FlowerShop<Rose> {
    public Rose getFlower() {
        return new Rose();
    }
}

class DaisyShop: FlowerShop<Daisy> {
    public Daisy getFlower() {
        return new Daisy();
    }
}

Question is "where is the flower shop?", answer is "rose shop there":

static FlowerShop<Flower> tellMeShopAddress() {
    return new RoseShop();
}

Contravariance

For instance you want to gift a flower to your girlfriend and your girlfrend likes any flowers. Can you consider her as a person who loves roses, or as a person who loves daisies? Yes, because if she loves any flower she would love both rose and daisy.

This is an example of the contravariance: you’re allowed to cast A<B> to A<C>, where C is subclass of B, if A consumes generic value. Contravariance is about consumers, that's why C# use keyword in for contravariance.

Types:

interface PrettyGirl<in TFavoriteFlower> where TFavoriteFlower: Flower {
    void takeGift(TFavoriteFlower flower);
}

class AnyFlowerLover: PrettyGirl<Flower> {
    public void takeGift(Flower flower) {
        Console.WriteLine("I like all flowers!");
    }
}

You're considering your girlfriend who loves any flower as someone who loves roses, and giving her a rose:

PrettyGirl<Rose> girlfriend = new AnyFlowerLover();
girlfriend.takeGift(new Rose());

Links

Consider there are two positions in an organization. Alice is a counter of chairs. And Bob is a storekeeper of the same chairs.

Contravariance. Now we can’t name Bob a storekeeper of furniture because he will not take a table to his store, he only stores chairs. But we can name him a storekeeper of purple chairs because a purple one is a chair. This is IBookkeeper<in T>, we allow assigning to more specific types and not to less. in stands for data flows into object.

Covarinace. On the contrary, we can name Alice a counter of furniture because it won’t affect her role. But we can’t name her a counter of red chairs because we will expect her not to count non-red chairs but she counts them. This is ICounter<out T>, allow implicit conversion to less specific, not to more specific. out stands for data flows out of object.

And invariance is when we can’t do both.

Related