Over the last couple of days I have read a number of blogs on the topic of covariance and contravariance, a new feature to be introduced in the upcoming C# 4.0 release. Whilst most of the blog posts were happy to provide a detailed account of what these terms mean and provide some simple examples, none really hit home how they differ from polymorphism (or assignment compatibility, the ability to assign a more specific type to a variable of a less specific type). That is, until I stumble across this excellent post on Eric Lippert’s blog which explains exactly that!

Covariance

For those of you who’s new to the topic of covariance and contravariance, here’s some code snippets to demonstrate what covariance means and the problem with it:

class Animal {}
class Dog : Animal {}
class Cat : Animal {}
void ChangeToDog(Animal[] animals)
{
animals[0] = new Dog(); // this throws runtime error because of type mismatch
}
void Main()
{
// this is fine, by virtue of polymorphism
// this is also known as 'type variance' because types of the two variables 'vary'
Animal animal = new Cat();
// this is also fine, this is called 'covariance'
// this works because the rules for arrays is co-ordinated with the rules for variance
// of the array element types
Animal[] animals = new Animal[] { new Cat() };
// here's the problem - because the ChangeToDog method accepts an array of Animal
// objects therefore it has to accept an array of Cat objects, and the subsequent
// assignment is also valid to the compiler because it follows the rules of
// variance, but clearly, you can't assign a Dog to an array of Cat objects!
ChangeToDog(new Cat[] { new Cat() });
}

Firstly, the problem demonstrated above throws a runtime error, which is not ideal as we C# developers prefer compile time errors. But the main issue here is that this is a WTF error because it catches you completely by surprise, as you’re writing this method there is no way for you to know that the consumer of your method will call it covariantly and cause a runtime exception to be thrown for an otherwise perfectly valid assignment.

So when generics became available in C# 2.0, the designers of C# took away the ability to use type variance with generics as you can see from the code snippets below:

class Animal {}
class Dog : Animal {}
class Cat : Animal {}
void ChangeToDog<T>(List<T> animals) where T : Animal
{
// compile time error
animals[0] = new Dog();
// same as above, compile time error
animals[0] = (T) new Dog();
// this compiles but throws a runtime type mismatch error as before, but no longer
// a WTF error because this has 'suspicious looking code' written all over it!
animals[0] = (T) (object) new Dog();
// this is pretty much the safest way to do the type of casting we want
if (animals is List<Dog>)
{
animals[0] = (T) (object) new Dog();
}
}
void Main()
{
// this no longer works!
List<Animal> animals = new List<Cat>() { new Cat() };
}

The inheritance relationship between a Cat and an Animal is not preserved because there was no covariance support for generics in C# 3.5.

In C# 4.0, you will be able to use covariance with interfaces on types that are only used at output positions which makes it safe and prevents the WTF exception from the first example from happening.

To use covariance in your code, you need to mark a type variable with the out keyword, here’s the new look IEnumerable<T> and IEnumerator<T> in C# 4.0:

In C# 4.0, you can use contravariance on a type that is only used in input positions which allows you to cast in the reverse direction (e.g. from Animal to Dog).

The in keyword can be used to mark an input type as contravariant, the limitation is that you are only allowed it for reference conversion, so no boxing allowed!

public interface IComparer<in T>
{
int Compare(T left, T right);
}

This means you will be able to use an IComparer<object> as though it was a IComparer<string>.

As you’d imagine, covariance and contravariance is also supported in the type parameters for generic delegates, the default Func and Action delegates have all been marked with the appropriate in or out keywords for each type parameter.

By continuing to use the site, you agree to the use of cookies. more information

The cookie settings on this website are set to "allow cookies" to give you the best browsing experience possible. If you continue to use this website without changing your cookie settings or you click "Accept" below then you are consenting to this.