Covariance and Contravariance in C# 4.0

You can become a serverless blackbelt. Enrol to my 4-week online workshop Production-Ready Serverless and gain hands-on experience building something from scratch using serverless technologies. At the end of the workshop, you should have a broader view of the challenges you will face as your serverless architecture matures and expands. You should also have a firm grasp on when serverless is a good fit for your system as well as common pitfalls you need to avoid. Sign up now and get 15% discount with the code yanprs15!

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!


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:

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

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


Conversely to covariance, contravariance reverses the inheritance relationship 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, contravariance is supported in generic delegates in the handler’s event argument as I have demonstrated above, but not in the type parameter T so this bit of code doesn’t compile:

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

Further reading

Eric Lippert’s 11-part (so far!) blog entries on Covariance and Contravariance

Joe Albahari’s presentation on What’s New in C# 4.0

Liked this article? Support me on Patreon and get direct help from me via a private Slack channel or 1-2-1 mentoring.
Subscribe to my newsletter

Hi, I’m Yan. I’m an AWS Serverless Hero and I help companies go faster for less by adopting serverless technologies successfully.

Are you struggling with serverless or need guidance on best practices? Do you want someone to review your architecture and help you avoid costly mistakes down the line? Whatever the case, I’m here to help.

Hire me.

Skill up your serverless game with this hands-on workshop.

My 4-week Production-Ready Serverless online workshop is back!

This course takes you through building a production-ready serverless web application from testing, deployment, security, all the way through to observability. The motivation for this course is to give you hands-on experience building something with serverless technologies while giving you a broader view of the challenges you will face as the architecture matures and expands.

We will start at the basics and give you a firm introduction to Lambda and all the relevant concepts and service features (including the latest announcements in 2020). And then gradually ramping up and cover a wide array of topics such as API security, testing strategies, CI/CD, secret management, and operational best practices for monitoring and troubleshooting.

If you enrol now you can also get 15% OFF with the promo code “yanprs15”.

Enrol now and SAVE 15%.

Check out my new podcast Real-World Serverless where I talk with engineers who are building amazing things with serverless technologies and discuss the real-world use cases and challenges they face. If you’re interested in what people are actually doing with serverless and what it’s really like to be working with serverless day-to-day, then this is the podcast for you.

Check out my new course, Learn you some Lambda best practice for great good! In this course, you will learn best practices for working with AWS Lambda in terms of performance, cost, security, scalability, resilience and observability. We will also cover latest features from re:Invent 2019 such as Provisioned Concurrency and Lambda Destinations. Enrol now and start learning!

Check out my video course, Complete Guide to AWS Step Functions. In this course, we’ll cover everything you need to know to use AWS Step Functions service effectively. There is something for everyone from beginners to more advanced users looking for design patterns and best practices. Enrol now and start learning!