Starting back in 2007 I read a series on Co/contra-variance in C# by Eric Lippert – Eric is on the compiler team at MS and one of my favorite bloggers to read I might add. At the time, I remember myself reading along with the words but my eyes completely glazing over. To be honest the concept mostly eluded me at the time, so I wanted to take this opportunity to describe the feature in a way that made the most sense to me.
It Seems to Logical…
Ask yourself the following questions:
- Is a List<string> also a List<object>?
- Is an IEnumerable<string> also an IEnumerable<object>?
As you’ll see below, the answers to these questions depends entirely on the version of the C# compiler you are using.
Turning our Questions into Tests
Naturally, the best way to answer the questions above is to turn them into a test. Let’s run them in 2008 and 2010 and check the results.
Visual Studio 2008 (.NET 3.5)
Visual Studio 2010 (.NET 4)
What have we discovered
In VS 2008 both tests failed. It turns out that T is T and not anything that is a superclass or subclass of T. The implications of this are that if you have a method that accepted an IEnumerable<Person>, but you have a List<SalesPerson>, you would think that you could surely pass your list into the method… unfortunately you would be incorrect.
In VS 2010 however, one of our tests actually passed. It turns out that in C# 3 a List<string> is in fact an IEnumerable<object>. So our simple scenario above would in fact allow us to pass a List<SalesPerson> to a method that accepts an IEnumerable<Person>.
The short explanation of this change is due to the fact that the IEnumerable<T> interface was actually changed in .NET 4 to be IEnumerable<out T> – a whole new reason to use the out keyword. This means that any method within this interface that uses the T parameter CAN ONLY RETURN T and never accept it into any method arguments. Other .NET 4 interfaces, such as IComparer<in T> are similar but opposite: any methods in this interface can only use T as a method input parameter but never the return type.
But why isn’t a List<string> also a List<object> even in .NET 4?
Ah so you noticed that one of our tests still failed even in VS 2010… Well if you read the recap above this will start to make sense. The List<T> class contains methods that use the T parameter as both inputs and outputs – it can insert T into the list and also retrieve an instance of T.
It would have to be declared as List<in out T> – which is not possible for two reasons:
- List<T> is a class, and generic variance support only applies to interface and delegate types.
- IList<in out T> is a syntax error. It would mean the same thing as it always has: IList<T> already means that it can be used as inputs and outputs.
Hopefully this quick introduction to generic variance was helpful. I am by no means an expert on this subject, so I encourage you to read Eric Lippert’s series above if this is something you find interesting!