How to work with covariance and contravariance in C#

Covariance and contravariance to provide polymorphic extension to delegates, arrays, and generics in C#

Covariance & Contravariance

The C# programming language provides support for variance in two ways: covariance and contravariance. Covariance and contravariance are supported only for reference types. Since C# 4.0, you can specify in and out parameters on generic types as well.

Covariance and contravariance are features added with C# 4.0 that provides polymorphic extension to delegates, arrays and even generics. The MSDN states: "Covariance and contravariance are terms that refer to the ability to use a less derived (less specific) or more derived type (more specific) than originally specified. Generic type parameters support covariance and contravariance to provide greater flexibility in assigning and using generic types."

Covariance, contravariance, and invariance

While covariance enables you to use a more derived type, contravariance allows you to take advantage of a more generic type that that was specified. On the contrary, invariance enables you to use only the type that was specified originally. In essence, an invariant generic type is neither covariant nor contravariant. In C#, the support for covariance and contravariance has been extended till generic types as well. The C# programming language enables you to annotate generic type parameters using both in and out annotations. This in turn enables you to specify if they should work covariantly or contravariantly.

Generics enable you to work with data-create typesafe collections and create classes and methods that can accept a type as a parameter. You can take advantage of generics to eliminate redundant code, enforce type safety, and promote code re-usability and maintainability. It should be noted that variance is applicable only on reference types and not on value type. In essence, a conversion of a value type to a reference type is a boxing conversion and hence it is not an example of variance.

Let’s now understand covariance, contravariance and invariance with code examples. First, we'll explore covariance with a simple example. Note that while object represents the base of all types in .Net, string represents a specific type. Hence, we may say that string is covariant to object.

The following code snippet illustrates how IEnumerable<out T> is defined in C#.

public interface IEnumerable<out T> : IEnumerable


       IEnumerator<T> GetEnumerator();


   public interface IEnumerator<out T> : IEnumerator


       bool MoveNext(); T Current { get; }


So, it is evident that IEnumerable<Derived> is also IEnumerable<Base> if Derived type extends the Base type. In other words, if you have two types -- Derived and Base -- with the former inherited from the latter, the following statement holds good:

IEnumerable<Base> obj = new List<Derived>();

Note that obj in the above code snippet, is an instance of type IEnumerable<Base>. Now, since Object is the base of all types in .Net, the following statement is also valid.

IEnumerable<Object> obj = new List<String>();

Here's another example of covariance - this time using arrays. Refer to the code snippet given below that assigns a string array to an array instance of type object.

object[] objArray = new String[100];

Let's now understand contravariance. Contravariance works the opposite way covariance works. Refer to the code snippet given next that illustrates the IComparer<T> interface.

public interface IComparer<in T>


int Compare(T left, T right);


Now suppose you have a comparer that can compare two objects. You can then use the same comparer to compare two strings as well. The reason this works is that the IComparer interface in .Net is contravariant in nature and its generic type parameter is marked with the in annotation. This is an example of contravariance. Another good example of contravariance is the Equals method that is used to compare two instances. If you have an Equals method that can compare two instances of a base type, you can use the same method to check for equality of two instances of a derived type (a type that extends the base type) as well. You can learn more on covariance and contravariance from this article.