BlackWaspTM

This web site uses cookies. By using the site you accept the cookie policy.This message is for compliance with the UK ICO law.

.NET Framework
.NET 4.0+

Generic Variance in .NET 4.0

With the release of .NET 4.0, Microsoft introduced covariance and contravariance to generic interfaces and delegates. Generic variance allows conversion and interchangeability of types in a logical manner that was not previously possible.

Contravariance

Contravariance is similar to covariance, except that the type order is reversed. Operations are said to be contravariant when the types that they utilise can be substituted with wider types. For example, we may have an operation that accepts a delegate with a Manager parameter. If the delegate can be switched for a similar one that accepts an Employee parameter the operation is said to be contravariant. This situation may initially seem counter-intuitive. However, as we will see in examples later, it can be very useful.

Invariance

When you have an operation that is neither covariant nor contravariant, it is said to be invariant. With invariance, no conversion or substitution is possible.

Generic Covariance and Contravariance

Prior to the .NET framework version 4.0, all generic types were invariant. Consider the following line of code:

IEnumerable<Employee> managers = new List<Manager>();

This code looks sensible. We are creating a list containing Manager objects. The generic List class implements the generic IEnumerable class and Managers can be held in Employee object, so it would seem that holding a List of Managers in an IEnumerable of Employees would be valid. However, as .NET 3.5 and earlier do not support covariance and the above does not represent a polymorphic relationship, it is illegal. Attempts to compile it fail, indicating that this is an invalid cast. If you try to explicitly cast between List<Manager> and IEnumerable<Employee> the code will compile but will throw an InvalidCastException at run-time.

Generic Covariance in .NET 4.0

.NET 4.0 introduces the possibility for variance for generic interfaces and delegates, where the type parameters are reference types. Covariance and contravariance is enabled for many of the standard interfaces and delegates, and can be added to your own types too. As an example, IEnumerable<T> is covariant for the T type parameter, meaning that the previous sample assignment, which does not work with earlier framework versions, is permitted in .NET 4.0.

Let's see another example that uses standard framework types. In the sample below we create a list of Manager objects and assign it to the managers variable, which is declared as IEnumerable<Manager>. Because IEnumerable<T> supports covariance, when this collection is passed to a method parameter of type IEnumerable<Employee>, the code compiles without error. We can then loop through all of the Employees, which are actually Managers, and output their names.

static void Main(string[] args)
{
    IEnumerable<Manager> managers = new List<Manager>
    {
        new Manager { Name = "Bob" },
        new Manager { Name = "Mel" },
        new Manager { Name = "Sam" }
    };
    OutputEmployeeNames(managers);
}

static void OutputEmployeeNames(IEnumerable<Employee> employees)
{
    foreach (Employee employee in employees)
    {
        Console.WriteLine(employee.Name);
    }
}

/* OUTPUT

Bob
Mel
Sam

*/

Implementing Generic Covariance

When developing covariant generic interfaces and delegates it is essential to maintain type safety. Unlike with the broken array covariance, we should not be able to compile code that applies invalid types that can cause run-time exceptions. To prevent this, the compiler enforces the rule that any use of the covariant type is directed out of the class and no input using the type is permitted. This means that the type defined in a covariant type parameter can be used in return values and read-only properties. It cannot be used in the declaration of method parameters or in writeable properties, preventing you from passing in an object of an incorrect type.

To declare a covariant type parameter, prefix it with the out keyword. If this is omitted, the type parameter remains invariant. You can see an example of the out keyword in the IEnumerable<T> interface. In .NET 3.5, the interface has the following declaration:

public interface IEnumerable<T> : IEnumerable

In .NET 4.0, this is updated to the following, giving covariance for the type parameter, "T".

public interface IEnumerable<out T> : IEnumerable

The full code for the IEnumerable<T> interface is as follows. Note that the T type is only used in one place; it is the return value for GetEnumerator. This means that the interface meets the requirement that the covariant type may only be passed out of the interface and never in.

public interface IEnumerable<T> : IEnumerable
{
    IEnumerator<T> GetEnumerator();
}
19 November 2011