Fernando Machado Píriz's Blog

Posts about digital transformation, enterprise architecture and related topics

Covariance and contravariance made easy

leave a comment »

One of the most important new features in C# 4.0 and .NET Framework 4.0 is the introduction into the language of covariance and contravariance. Several times I have spoken about covariance and contravariance while presenting C# and .NET Framework 4.0 news, and also in the old blog, but I am not very sure that all attendees end by easily getting these concepts. Until now I have started from formal definitions of covariance and contravariance, and then show how to implement them in C#. Probably that one is not the best approach, so now I will try to explain those concepts in a different way. Here we go.

Take a look on the following classes Animal and Cat inheriting from Animal:

class Animal { }
class Cat : Animal { }

The following declarations are valid in any C# version:

Cat kitty = new Cat();
Animal animal = cat;

Every Cat is an Animal –that is what Cat inherits from Animal means-, so I can assign the variable kitty to the variable animal. There is nothing new until now.

Look now to what happens when I try to do something similar with enumerable of Animal and enumerable of Cat:

IEnumerable<Cat> cats = new List<Cat>();
IEnumerable<Animal> animals = cats;

Every Cat is an Animal, so intuitively any enumerable of Cat is an enumerable of Animal, right? Wrong, at least for all compilers before C# 4.0; they say they cannot convert IEnumerable<Cat> into IEnumerable<Animal> and ask me if I am missing a cast.

clip_image001

I can add the cast, but that will be an unsafe cast. I mean, the program will compile, but I will see an InvalidCastException at runtime when trying to do the assignment.

Okay, you and me will agree that even if the C# compiler does not accept that every enumerable of Cat is also an enumerable of Animal, intuitively we accept that sentence, in the same way we accept that every Cat is an Animal.

C# 4.0 solves the conflict. The code fragment above happily compiles and does not generate any exception at runtime, matching our intuition.

Why the same code that compile in C# 4.0 does not compile in previous versions?

Before C# 4.0 IEnumerable interface was declared as:

public interface IEnumerable<T> : IEnumerable

While in C# 4.0 it is declared as:

public interface IEnumerable<out T> : IEnumerable

Note the out keyword besides the T type parameter: it is used to indicate that IEnumerable is covariant in respect of T. Generally speaking, given S<T>, being S in respect of T implies that, if the assignment Y ← X is valid when X inherits from Y, then the assignment S<Y> ← S<X> is also valid.

Let’s take a look now to another code fragment involving actions –actions are delegates to functions with the form void Action<T>(T)– on Animal and Cat.

Action<Animal> doToAnimal = target => { Console.WriteLine(target.GetType()); };
Action<Cat> doToCat = doToAnimal;
doToCat(new Cat());

Once again, since every Cat is an Animal, an Action<Animal> I can do to an Animal should also be an Action<Cat> that I can do to a Cat. Observe that even while the sentence is reasonable to say, parameter types are the other way around than in the previous case: there I was assigning an enumerable defined in terms of Cat to an enumerable defined in terms of an Animal, while in this case I am assigning an action defined in terms of Animal to an action defined in terms of Cat.

Even if the sentence is reasonable, compilers before C# 4.0 do not like the assignment and fail with a similar message than in the previous case: cannot convert an Action<Animal> into an Action<Cat>; in this case no questions about cast.

clip_image002

Once again C# 4.0 solves the problem, and the code fragment above does compile. Let’s see how actions are declared:

Before C# 4.0 the Action delegate was declared as follows:

public delegate void Action<T>(T obj);

While in C# 4.0 is now declared as:

public delegate void Action<in T>(T obj);

Observe now the keyword in besides type parameter T: it is used to indicate that delegate Action is contravariant in respect of T. Generally speaking, given S<T>, being S in respect of T implies that, if the assignment Y ← X is valid when X inherits from Y, then the assignment S<X> ← S<Y> is also valid.

Summarizing, covariance in C# 4.0 allows a method having a result of a type derived from the type parameter defined in the interface. In this same way, it allows assignments of generic types that, being intuitive, were not allowed before, such as IEnumerable<Animal>IEnumerable<Cat>, when Cat inherits from Animal.

Note that IEnumerable can only return instances, it does not receive instances as parameters. That is why the keyword to declare IEnumerable covariant in respect of T is out.

Analogously, contravariance allows a method to have parameters of a type ancestor of the type specified as type parameter in the delegate. This way, it also allows assignments that even if being intuitive, were not possible before, like Action<Cat>Action<Animal>. Observe that in this case Action can only receive instances as parameters, it does not return instances. That is why the keyword to declare Action contravariant in respect of T is in.

In this post I am showing an example of covariance with a generic interface and an example of contravariance with a generic delegate; in an upcoming post I will show different examples and will list which interfaces and delegates in .NET Framework 4.0 are covariant and which ones are contravariant.

Sample code can be downloaded from here. To test it in compilers earlier that C# 4.0 and see the difference, change the target framework in project properties:

clip_image003

Hope you have enjoyed this post. See you.

Advertisements

Written by fernandomachadopiriz

April 16, 2010 at 3:31 am

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 )

Google+ photo

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

Connecting to %s

%d bloggers like this: