Covariance and Contravariance in C# 4.0

Over the last cou­ple of days I have read a num­ber of blogs on the top­ic of covari­ance and con­travari­ance, a new fea­ture to be intro­duced in the upcom­ing C# 4.0 release. Whilst most of the blog posts were hap­py to pro­vide a detailed account of what these terms mean and pro­vide some sim­ple exam­ples, none real­ly hit home how they dif­fer from poly­mor­phism (or assign­ment com­pat­i­bil­i­ty, the abil­i­ty to assign a more spe­cif­ic type to a vari­able of a less spe­cif­ic type). That is, until I stum­ble across this excel­lent post on Eric Lippert’s blog which explains exact­ly that!

Covariance

For those of you who’s new to the top­ic of covari­ance and con­travari­ance, here’s some code snip­pets to demon­strate what covari­ance means and the prob­lem 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() });
}

First­ly, the prob­lem demon­strat­ed above throws a run­time error, which is not ide­al as we C# devel­op­ers pre­fer com­pile time errors. But the main issue here is that this is a WTF error because it catch­es you com­plete­ly by sur­prise, as you’re writ­ing this method there is no way for you to know that the con­sumer of your method will call it covari­ant­ly and cause a run­time excep­tion to be thrown for an oth­er­wise per­fect­ly valid assign­ment.

So when gener­ics became avail­able in C# 2.0, the design­ers of C# took away the abil­i­ty to use type vari­ance with gener­ics as you can see from the code snip­pets 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 inher­i­tance rela­tion­ship between a Cat and an Ani­mal is not pre­served because there was no covari­ance sup­port for gener­ics in C# 3.5.

In C# 4.0, you will be able to use covari­ance with inter­faces on types that are only used at out­put posi­tions which makes it safe and pre­vents the WTF excep­tion from the first exam­ple from hap­pen­ing.

To use covari­ance in your code, you need to mark a type vari­able with the out key­word, here’s the new look IEnumerable<T> and IEnumerator<T> in C# 4.0:

public interface IEnumerable<out T> : IEnumerable
{
    IEnumerator<T> GetEnumerator();
}

public interface IEnumerator<out T> : IEnumerator
{
    bool MoveNext();
    T Current { get; }
}

Contravariance

Con­verse­ly to covari­ance, con­travari­ance revers­es the inher­i­tance rela­tion­ship of T:

public class CustomEventArgs : EventArgs { }
// delegate with handler which accepts type CustomEventArgs
public EventHandler<CustomEventArgs> MyDelegate;
// handler with a less specific type EventArgs
public void MyHandler(object sender, EventArgs e) { }

void Main()
{
    // this works thanks to contravariance
    MyDelegate += MyHandler;
}

Right now, con­travari­ance is sup­port­ed in gener­ic del­e­gates in the handler’s event argu­ment as I have demon­strat­ed above, but not in the type para­me­ter T so this bit of code doesn’t com­pile:

class Person {}
class Student : Person {}

// throws compile error for type mismatch because Action<T> is not contravariant in T
Action<Student> studentAction = (Person p) => { /* do something */ };

In C# 4.0, you can use con­travari­ance on a type that is only used in input posi­tions which allows you to cast in the reverse direc­tion (e.g. from Ani­mal to Dog).

The in key­word can be used to mark an input type as con­travari­ant, the lim­i­ta­tion is that you are only allowed it for ref­er­ence con­ver­sion, so no box­ing 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 imag­ine, covari­ance and con­travari­ance is also sup­port­ed in the type para­me­ters for gener­ic del­e­gates, the default Func and Action del­e­gates have all been marked with the appro­pri­ate in or out key­words for each type para­me­ter.

Further reading

Eric Lippert’s 11-part (so far!) blog entries on Covari­ance and Con­travari­ance

Joe Albahari’s pre­sen­ta­tion on What’s New in C# 4.0