.NET 4.0 - Covariance and contravariance

 
1/5/2011
.NET, C#
0 Comments

Today I took a look at the new covariance and contravariance features of .NET 4. In this post I will explain the theoretical background and will provide some practical examples.
The code snippets cover most of the features of CLR 1 to CLR 4.

Theoretical background

Covariance and contravariance are terms known from category theory. A covariant functor is a morphism between two categories, which preserves the structure.
If we use numbers as categories and numeric ordering as structure, the function f(x) = x + 10 preserves the ordering and therefore is covariant. The function f(x) = -x reverses the ordering and thus is contravariant.

Examples

In my examples I use the following class hierarchy:

Class Hierarchy

Array covariance

The transformation is T -> T[], the structure is 'assignment compatibility'. If 'assignment compatibility' is preserved the transformation is covariant.
Since every Car is a Vehicle, we can assign a Car to a Vehicle variable. The same is true for arrays of Cars/Vehicles, so arrays are covariant.

Vehicle vehicle = new Car();
Vehicle[] vehicles = new Car[] { };

Array covariance only works for reference types. Arrays of value types are invariant:

object time = new DateTime();

// Does not compile: Cannot convert from value type array to object array object[] times = new DateTime[] ;

Delegate covariance

Delegates are covariant in their return type. Image the following delegates:

delegate Vehicle VehicleFactoryDelegate();
delegate Car CarFactoryDelegate();

The VehicleFactoryDelegate produces Vehicles. Since every Car is a Vehicle, we can assign a CarFactoryDelegate to a VehicleFactoryDelegate:

VehicleFactoryDelegate vehicleFactory = CreateVehicle;
CarFactoryDelegate carFactory = CreateCar;

vehicleFactory = CreateCar;

// Does not compile: vehicleFactory produces vehicles, but carFactory only produces cars
carFactory = CreateVehicle;

Delegate contravariance

Delegates are contravariant in their parameter types. Image the following delegates:

delegate void VehicleProcessorDelegate(Vehicle vehicle);
delegate void CarProcessorDelegate(Car car);

The CarProcessorDelegate processes Cars. Since every Car is a Vehicle, we can assign a VehicleProcessorDelegate to a CarProcessorDelegate:

VehicleProcessorDelegate vehicleProcessor = ProcessVehicle;
CarProcessorDelegate carProcessor = ProcessCar;

carProcessor = ProcessVehicle;

// Does not compile: carProcessor can only work with cars, but vehicleProcessor is able to process vehicles
vehicleProcessor = ProcessCar;

.NET 4.0: Generics covariance

With .NET two new keywords out/in where introduced. They can be used to explicitly enable co-/contravariance for generic type parameters. Out is used to enable covariance in the returned type (note the out-keyword in System.Func<out TResult>):

Func<Vehicle> vehicleFactory = () => new Vehicle();
Func<Car> carFactory = () => new Car();

// Only CLR 4
vehicleFactory = carFactory;

// Does not compile: vehicleFactory produces vehicles, but carFactory only produces cars
carFactory = vehicleFactory;

Use the out-keyword in generic interfaces to enable covariance:

interface ICovariant<out T>
{
    T Create();
}

.NET 4.0: Generics contravariance

In is used to enable contravariance in parameter types (note the in-keyword in System.Action<in T>):

Action<Vehicle> vehicleProcessor = v => { };
Action<Car> carProcessor = c => { };

// Only CLR 4
carProcessor = vehicleProcessor;

// Does not compile: carProcessor can only work with cars, but vehicleProcessor is able to process vehicles
vehicleProcessor = carProcessor;

Use the in-keyword in generic interfaces to enable contravariance:

interface IContravariant<in T>
{
  void Process(T t);
}

Downloads

Feedly Feedly Tweet


Related posts


Comments