Seamless Generic Type Casting

October 28, 2011

In Array Covariance in C#, we saw that a List<Apple> cannot be assigned to a variable declared as List<Object>, even though all Apples are Objects. If we were allowed to do that, we could get runtime type exceptions as we work with the list contents.

The compiler has to prevent us from these assignments because List<T>'s public interface includes some subtle constraints: List<Apple> is actually a more constrained type than List<Object>. You can put anything into a List<Object> and get it back out again. You can't put just anything into a List<Apple>; what would you expect to happen when you tried to read it back out? Therefore, List<Apple> is more constrained than List<Object> and we can't treat List<Apple> as a subtype of List<Object>.

Still, it's tempting to ask, is there some other generic type Foo<T> where Foo<Apple> can be treated as a Foo<Object>? It turns out the answer is yes!

Let's say your application is sending messages to another process over a message bus, and that you've implemented a helper method that sends a whole collection of messages instead of just one:

Unfortunately, this won't even compile. We're trying to send a list of RemoveOrderLineItem messages, but the SendAll method accepts a List<IMessage>. Fortunately, this starts to compile if we change the definition of SendAll to receive an IEnumerable<IMessage> instead of a List<T>:

What's so special about IEnumerable<IMessage> that lets us seamlessly cast from a List<RemoveOrderLineItem>? Remember, the reason that the compile originally failed had to do with the fact that the object we were passing to SendAll had more constraints than the type declared on the receiving end. With IEnumerable<IMessage>, it's a different story. Now, the only thing that SendAll can do with our collection is get values out of it, and we know all such values will be individually compatable with IMessage.

We're able to do this because, as of .NET 4, IEnumerable<T> has an updated definition:

The out keyword was added to the generic type parameter T. Using this keyword is a promise. "I promise that for all the members in this interface, T will only appear in the return types, never appearing in any inputs." When we use the out keyword, we're saying that T is "covariant".

The in keyword makes a similar promise: the type parameter T could appear in the types of input arguments, but never in the return types. When we use the in keyword, we're saying that T is "contravariant".

In addition to sanity checking your promise, the compiler is now free to treat any IEnumerable<RemoveOrderLineItem> as a IEnumerable<IMessage>, without having to cast each individual item first. IEnumerable<RemoveOrderLineItem> is truly a subtype of IEnumerable<IMessage>.

Introducing these two keywords to the language was a nonbreaking change, with the main effect being that people encountered fewer compiler errors, usually without knowing it. When you design your own libraries, consider whether your interfaces deserve to take part in this style of casting. Beware, though, that removing the keywords later on can be a breaking change from the point of view of your library's consumer!

In my next post, we'll see how these keywords can be used to glue together delegates.