Note to Self: Covariance and Contravariance in a Nutshell

Tuesday, October 12, 2010 / Posted by Luke Puplett /

On Channel9, the academic interviews are very stimulating but they tend to confuse a simple concept in theoretical mumbo-jumbo.

Covariance is the ability to upcast a collection of types to a collection of more primitive classes.

Sorry about the small font, some crappy CSS somewhere.

IEnumerable<Truck> trucks = TruckRepository.Trucks.Where(t => t.Color == Red);

IEnumerable<Vehicle> vehicles = trucks as IEnumerable<Vehicle>;

But it only works with IEnumerable<T> because the collection is read-only. If it didn’t have this constraint, then we’d be able to do this to a List and then add a Car, and that’d be bad because its still really a collection of Truck.

Contravariance is obviously the opposite, when things go in (and so less to do with collections), the type can be cast to be more specific (say, Action<Vehicle> to Action<Truck> and it will happily accept a Truck because its a vehicle.

Func<T, TResult> is more interesting because its got generic T in and TResult out types. This means that the T is contravariant on the way in and the TResult is covariant as it comes out.

Variance doesn’t come automatically.

Should you wish to allow covariance, the T must be marked with the out keyword; out T. And obviously in T defines and contravariant generic type param. You can’t put out T on an interface method because it method params are implicitly inbound.

Co out. Contra in.

Microsoft have added out and in to a whole bunch of interfaces and delegates which is why it appears to come automatically with .NET 4.0.

Restrictions (from MSDN):

  • In the .NET Framework version 4, variant type parameters are restricted to generic interface and generic delegate types.

  • A generic interface or generic delegate type can have both covariant and contravariant type parameters.

  • Variance applies only to reference types; if you specify a value type for a variant type parameter, that type parameter is invariant for the resulting constructed type.

  • Variance does not apply to delegate combination. That is, given two delegates of types Action<Derived> andAction<Base> (Action(Of Derived) and Action(Of Base) in Visual Basic), you cannot combine the second delegate with the first although the result would be type safe. Variance allows the second delegate to be assigned to a variable of typeAction<Derived>, but delegates can combine only if their types match exactly.



Comment by Luke Puplett on Monday, January 10, 2011

Just to clarify why contravariance works with Action - very simply:

Action is a delegate where the T is of course the inbound type, so although it looks backward to be able to assign a downcast instance to a more "primitive" variable, when it comes to compile time, the type T ends up an "in".

Post a Comment