The .NET languages support the concepts of covariance and contravariance. These concepts enable you to reduce restrictions on strong typing when working with delegates, generics, or generic collections of objects. In certain situations, decreasing the type restrictions might increase your ability to reuse code and objects and decrease the need to do a lot of casting or converting to provide the right type to a method.
Covariance is the ability to use a more derived type than that which was originally specified by an interface or function signature. For example, you could assign a list of strings to a generic list that only takes objects if that list supports covariance (because strings inherit from objects and are thus more derived). Contravariance is similar; it is the ability to use a less-derived type for a given parameter or return value. That is, you might assign an object type as the return type for a method that returns a string (provided that method supports contravariance).
It is important to note that the target type has to support covariance or contravariance. This is not a change to the entire language. Instead, it introduces a couple new keywords to allow support for these concepts when appropriate.
Many of the generic interfaces in the latest version of the .NET Framework now support variance. This includes the interfaces IEnumerable<T>
and IEnumerator<T>
(among others) that support covariance. This means you can have support for variance inside your collections.
For example, you might have a list of Manager
objects. Recall that Manager
derives from Employee
. Therefore, if you need to work with the Manager
list as an Employee
collection, you can do so using List
and the IEnumerable
interface. The following code shows an example.
C#
IEnumerable<Manager> managers = new List<Manager>();
IEnumerable<Employee> employees = managers;
VB
Dim managers As IEnumerable(Of Manager) = New List(Of Manager)()
Dim employees As IEnumerable(Of Employee) = managers
The preceding code compiles and executes because Manager
inherits from Employee
and is thus more derived. Using covariance, you can use a list of Manager
objects with a list of Employee
objects. For example, you might have a method that takes a list of Employee
objects as a parameter. Using covariance support, you can pass the Manager
list instead.
Support for variance has additional ramifications for your coding. These include the following:
Custom generic classes—If you create your own custom generic classes, you can declare support for variance. You do so at the interface level using the out
(covariant) and in
(contravariant) keywords on generic type parameters.
Delegate variance—Using variance, you can assign methods to delegates that return more derived types (covariance). You can also assign those methods that accept parameters that have a less derived type (contravariance).
Func and Action—The generic delegates Func<>
and Action<>
now support variance. This enables you to more easily use these delegates with other types (and thus increase the flexibility of your code).