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.

What is Variance?

In this article I will describe the new generic variance features in the .NET framework version 4.0. Variance, when applied in computer programming, describe the manner in which operations that use different types are interchangeable, convertible or equivalent. There are three types of variance considered in this article. These are covariance, contravariance and invariance. Before we look at what these are, we first need a basic understanding of type ordering.

Type Ordering

In object-oriented programming languages like C#, you can create inheritance hierarchies. These include base types, or supertypes, which define some functionality. Subtypes inherit from supertypes, automatically obtaining all of their parent's functionality. The subtype may add further functionality and override members that it has inherited. Multiple classes can be organised into complex hierarchies in this manner.

In inheritance relationships, the base type is said to be wider, or larger, than its subtypes. The subtypes in turn are said to be narrower or smaller. When comparing a type to itself, you can say that the type is equivalent in size. If you have two types where neither is a supertype of the other, the types are said to be unrelated, even if they share a base class. When working with instances of types, polymorphism means that a smaller type can be represented by a larger type. A larger type instance cannot be held as a smaller type.

We can see this with an example inheritance hierarchy. In the diagram below we have four classes. These are Employee, Manager, AreaManager and CasualWorker.

Employee Inheritance Hierarchy

In the diagram, Employee is a supertype of all of the other types. Employee is therefore the widest type present. We can assign an instance of any of the four types to an Employee variable. The Manager class is narrower than Employee but wider than AreaManager so we can hold an AreaManager in a Manager variable but cannot assign a Manager object to an AreaManager variable. The CasualWorker class is narrower than Employee but is unrelated to Manager and AreaManager. A CasualWorker variable cannot, therefore, hold an instance of any of the other three types.

During this article we will see several code samples that use the above classes. To recreate the samples, you will need to define the four types of employee. To do so, use the following code:

public class Employee
{
    public string Name { get; set; }
    public decimal Salary { get; set; }
}

public class Manager : Employee 
{
    public IList<Employee> Subordinates { get; set; }
}

public class AreaManager : Manager
{
    public string Area { get; set; }
}

public class CasualWorker : Employee
{
    public int DaysNoticeRequired { get; set; }
}

Covariance

Variance deals with the compatibility of operations that use types. An operation is said to be covariant when you can substitute a similar operation that uses a narrower type. This is said to preserve the same type order as the underlying types. For example, polymorphism dictates that we can provide a Manager object when an Employee object is expected. If we have a function that returns a Manager and we can use this when a function that returns and Employee is required, we can say that the function is covariant. This is not polymorphism because the Manager function does not inherit from the Employee function, even though Manager inherits from Employee.

This description is quite complicated so let's consider some examples:

  • We may define a delegate that returns an Employee object when called. The delegate is covariant if we can substitute this delegate for one that creates and returns a Manager, AreaManager or CasualWorker.
  • Imagine that we have a variable that is declared as an array of Employees. If we assign an array of CasualWorker objects to the variable we can say that arrays are covariant. This is not the same as using polymorphism to add a CasualWorker to an Employee array. CasualWorker is a subtype of Employee but the array type CasualWorker[] is not a subtype of Employee[].
  • We may create a generic decorator class that adds undo and redo functionality to a type. This would be covariant if we could assign an Undoable<AreaManager> object to a variable of the type Undoable<Employee>.

The second example is of some interest as C# arrays that hold reference types have been covariant since the first version of the .NET framework. This means that you can create an array of one type and assign it to a variable that holds an array of a base class. We can see this in the code sample below. Here an array of Managers is represented as an array of Employees.

Employee[] employees = new Manager[10];
employees[0] = new Manager();
employees[1] = new AreaManager();

As you can see, as both the Manager and AreaManager inherit from Employee, it is possible to add Managers and AreaManagers to the array. Unfortunately, array covariance in .NET is not type safe. In the above code the underlying array is always an array of Managers. When represented as an Employee array, the compiler assumes that it is acceptable to add any employee type to the array.

The following code attempts to add a CasualWorker to the array. The compiler thinks this is valid because a true Employee array can include CasualWorkers. However, as the underlying array is designed to hold Managers, CasualWorkers are incompatible and an ArrayTypeMismatchException is thrown.

employees[2] = new CasualWorker(); // ArrayTypeMismatchException at run-time

Key to covariance is the previously mentioned preservation of the type order. This means that although it is possible to assign a Manager array to an Employee array variable, the reverse is not true. Therefore, the following code does not compile.

Manager[] managers = new Employee[10];
19 November 2011