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.

Covariant Type Example

To complete the investigation of generic type covariance we'll create a covariant interface. This interface will be implemented by classes that provide a simple version of the factory method pattern. The interface includes a single method named, "Generate", which accepts a string parameter and returns an object of the covariant type.

The interface is as follows. Note the use of the out keyword on the type parameter:

interface IFactory<out T>
{
    T Generate(string name);
}

We'll create one implementation of the IFactory<T> interface that generates Employee objects with the name provided in the parameter. The code for this is shown below. The generate method simply creates a new object and sets its Name property. The class has some generic constraints. These are not a requirement of classes that implement a covariant interface. They are necessary in this example to ensure that we have access to the Name property and that we can instantiate the object using the default constructor.

class Factory<T> : IFactory<T> where T : Employee, new()
{
    public T Generate(string name)
    {
        return new T { Name = name };
    }
}

We can now use the interface and class to demonstrate covariance. In the code shown below, we create a Factory<AreaManager> and assign it to an IFactory<Employee>. This is valid because it maintains the type ordering. It would not have been valid to assign a Factory<Employee> to an IFactory<AreaManager> variable. Run the code to see it operate correctly, then try removing the out keyword from the interface. This will disable the covariance and prevent the program from compiling.

IFactory<Employee> factory;
factory = new Factory<AreaManager>();

AreaManager sam = factory.Generate("Sam") as AreaManager;

Console.WriteLine(sam.Name);    // Sam

Generic Contravariance in .NET 4.0

As with covariance, contravariance can be applied to generic type parameters for use with reference types but not value types. The standard framework classes include many examples of contravariance with arguably the most commonly used being applied to delegates, such as the generic Action delegates.

We can show delegate contravariance with an example. In the code below, we declare three Actions. The first accepts an Employee and outputs its Name property. The other two Action delegates perform similar functions for Managers and AreaManagers. We also have a method that has a single parameter to which you can pass an Action<AreaManager>. This method executes the delegate.

We know that an action that can be performed upon an Employee or a Manager can also be applied to an AreaManager; any methods or properties of the provided Employee used by the delegate are also implemented by AreaManager. The AreaManager has more members but these would not be visible to an Action<Employee> so we know that the operation would be type-safe. With generic contravariance this is possible, as the example shows. Each of the delegates is passed to the ApplyAction method and each is executed successfully.

Note that in this case we are passing an Action<Employee> and an Action<Manager> to a parameter that expects an Action<AreaManager>. We are passing a wider type where a narrower type is expected. This is reversing the type order, thus demonstrating contravariance.

static void Main(string[] args)
{
    Action<Employee> employeeAction = e => Console.WriteLine("Employee: " + e.Name);
    Action<Manager> managerAction = m => Console.WriteLine("Manager: " + m.Name);
    Action<AreaManager> areaMgrAction = am => Console.WriteLine("Area Manager: " + am.Name);

    AreaManager sam = new AreaManager { Name = "Sam" };

    ApplyAction(employeeAction, sam);
    ApplyAction(managerAction, sam);
    ApplyAction(areaMgrAction, sam);
}

static void ApplyAction(Action<AreaManager> action, AreaManager am)
{
    action(am);
}

/* OUTPUT

Employee: Sam
Manager: Sam
Area Manager: Sam

*/

Implementing Generic Contravariance

When you create your own contravariant generic type parameters, the compiler ensures that they are type-safe by only allowing those types to be used when passing values into the interface. You can use the types in parameters and write-only properties but cannot use them for return values or readable properties of the interface. This prevents you from returning values of wider types than expected and using properties or methods of the narrower type that are not present in its supertypes. NB: The restrictions apply only to the interface and not to classes that implement the interface.

To declare a contravariant type parameter, prefix it with the in keyword. Without this keyword the type parameter is invariant. We can see an example of this syntax in the Action<T> delegate. The definition of Action<T> in .NET 3.5 is as follows:

public delegate void Action<T>(T obj)

In .NET 4.0, this declaration is updated to:

public delegate void Action<in T>(T obj)
19 November 2011