In Array Covariance in C#, we saw that a
List<Apple> cannot be assigned to a variable declared as
List<Object>, even though all
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
Still, it's tempting to ask, is there some other generic type
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
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
We're able to do this because, as of .NET 4,
IEnumerable<T> has an updated definition:
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".
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
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.