Covariance and contravariance, part 2: Contravariance in interfaces

This is part two in a series about variance – contravariance and covariance.

  1. Arrays and lists
  2. Contravariance in interfaces
  3. Covariance in interfaces

In the previous article I introduced the concepts of variance and type size.  I also gave arrays as an example of covariance.

In this article I’ll give an example of contravariance, and go into more about how covariance and contravariance are different and how I remember which is which.

Loose definitions and remembering which is which

  • Covariance is to do with the outputs of something, e.g. the return types of a method, and allows you to use those outputs in a context that appears to be about a bigger type.
  • Contravariance is to do with the inputs to something, e.g. the parameter types for a method, and allows you to pass in something where a smaller type is expected.

I have a silly mnemonic, which I mention here in case it’s useful.  The first letter difference between ‘covariance’ and ‘contravariance’ involves the N in ‘contravariance’.  N is half of the word ‘in’, which helps me to remember that contravariance is about inputs.

Example of contravariance – interfaces

I’m going to slightly add to the types from the previous variance post.  There’s now a Weight property in Animal, and a WingSpan property in Bird, both of which are integers.  There’s also a constructor for Bird that takes values for these new properties, and ToString() methods that print out what you’d expect.

We will create a list of Birds which we want to sort.  To do this we will create a class that implements the IComparer<T> interface, but for T=Animal rather than T=Bird.  We’ll then pass an instance of this class to List<T>.Sort(IComparer<T>).

Here is the comparer

1
2
3
4
5
6
7
private class WeightComparer : IComparer<Animal>
{
	public int Compare(Animal x, Animal y)
	{
		return x.Weight.CompareTo(y.Weight);
	}
}

And here is it being used:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
List<Bird> birds = new List<Bird> { new Bird(Weight:30, WingSpan:1000), new Bird(Weight:20, WingSpan:50) };

Console.WriteLine("\nBefore sorting");
foreach (var bird in birds)
{
	Console.WriteLine(bird.ToString());
}

var weightComparer = new WeightComparer();
birds.Sort(weightComparer);

Console.WriteLine("\nAfter sorting");
foreach (var bird in birds)
{
	Console.WriteLine(bird.ToString());
}

This gives the following results:

  • Before sorting
    • {Bird, wing span:1000, weight:30}
    • {Bird, wing span:50, weight:20}
  • After sorting
    • {Bird, wing span:50, weight:20}
    • {Bird, wing span:1000, weight:30}

As you can see, the list is sorted.  The sorting uses only the Weight property of the Birds, because the comparer is expecting Animals and not Birds, and so doesn’t know that WingSpan exists.

The way into the contravariance is line 10 of the second listing.  The argument passed to birds.Sort() is, strictly speaking, wrong.  Specifically, it is of the wrong type.  Birds.Sort() expects to be passed something of type IComparer<Bird> and instead we pass it something of type IComparer<Animal>.

As in the previous article, it’s important to remember that IComparer<Bird> is not derived from IComparer<Animal>, even though Bird is derived from Animal.  However, IComparer<Animal> is a bigger type than IComparer<Bird>.  An IComparer<Animal> could compare pairs of Birds, but it could also compare pairs of Dogs, or a Bird and a Dog etc.  All these types will have all the properties and methods that an Animal does, so an IComparer<Animal> will have access to all the information it needs to compare them.

It’s important to note here what the type variable applies to, by looking at the definition of WeightComparer.  The type variable T has the value Animal, and all the uses of the type Animal in the class relate to inputs.  (In this case, just the inputs to the Compare method.)  There are no return types that use T.

So, the root of the example is that IComparer<T> is contravariant in T.  This enables line 10 to use the ‘wrong’ argument.

In a later article I will show interfaces with type variables, that are covariant in those variable types. I.e. type variables can be covariant, contravariant or neither.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s