Covariance And Contravariance in C#

I hope many of you have come across the two buzz words "Co-Variance" and "Contra-Variance" while browsing through .NET portals. In this article, I will try to give an introduction to the concepts behind these buzz words.

Covariance and Contravariance are polymorphism extension to the arrays, delegates and generics. It provides implicit reference conversion for Arrays, Delegates and Generic parameter types. Covariance preserves the assignment compatibility and Contravariance opposite the covariance functionality.

Assignment Compatibility

Actually assignment compatibility is not new to the C# programmer. It is only the word "Assignment Compatibility" that is new.

Have a look at the following sample code:

String stringObject = "A String Object";
Object anObject = stringObject;

An Object of a derived class (stringObject) is being assigned to a variable of a base class (anObject).

Array Covariance

The Array data type has supported Covariance since .NET 1.0. While working with arrays, you can successfully make the following assignment.

object[] objArray = new String[10];

The code shown above assigns a string array to an object array variable. That is, a more derived class object's array (String []) is being assigned to the less derived class object array variable (object []). This is called covariance in array.

Array Covariance is not safe. Consider the following statement

objArray[0] = 5;

That statement will not report any compile time errors. But at runtime, it causes an ArrayTypeMismatchException exception. It is due to the fact that the objArray variable actually holds a reference of a string Array.

Delegate Covariance

This type of variance is also called method group variance. It allows Delegate instances to return more derived class types than what is specified in the type declaration.

In the following example of covariance, a string returning function is being assigned to a delegate which is declared to return object type.

static string GetString() { return ""; }

static void Main()
{
    Func<object> delegateObject = GetString;
   //String strObject = delegateObject();
}

Contravariant  Delegate

The Contravariance Delegates reverse the Covariance functionality. It allows a method that has parameter types less derived than what is specified in the delegate.

In the following example a delegate specifies a parameter type as string. Still we can assign a method that has object as parameter.

static void SetObject(object objectParameter) { }
static void SetString(string stringParameter) { }
static void Main()
{
    Action<string> del2 = SetObject;
    .....
}

What is new in .NET 4.0 about Delegate Variant?

Implicit conversion between generic delegates is supported now in .NET 4.0. Previously the compiler would report an error for the following statement.

static void Main()
{
    Func<string> del3 = GetString;
    Func<object> del4 = del3; // Compiler error here until C# 4.0.
}

Variance for generic type Parameters

In .NET 4.0 Microsoft introduced implicit type conversion between interface instances that have different type arguments. This means, an interface instance that has method with more derived return types than originally specified ( Covariance) or that has methods with less derived parameter types (Contravariance).

Variant generic interfaces can be declared using out keywords for generic type parameters.

Conditions for creating generic covariant parameter for interface types.

ref and out parameters cannot be declared as a variant. Only reference data type can be declared as variant, not the value types. For example, IEnumerable<int> cannot be implicitly converted to IEnumerable<object>, because integers are represented by a value type.

The generic type can be used only as a return type of interface methods and not used as a type of method arguments.

interface ICovariant<out R>
{
    R GetSomething();
    // The following statement generates a compiler error.
    // void SetSometing(R sampleArg);
}

If you have a contravariant generic delegate as a method parameter, you can use the type as a generic type parameter for the delegate. 

interface ICovariant<out R>
{
    void DoSomething(Action<R> callback);
}

The Contravariant generic type parameter can be declared using "in" keyword.  

It is also possible to support both covariance and contravariance in the same interface, but for different type parameters, as shown in the following code example. 

interface IVariant<out R, in A>
{
    R GetSomething();
    void SetSomething(A sampleArg);
    R GetSetSometings(A sampleArg);
}

More importantly .NET 4.0 introduces variance support for several existing generic interfaces.

Before .NET 4.0, the following code causes compilation error, but now you can use strings instead of objects, as shown in the example, because the IEnumerable(Of T) interface is covariant.

IEnumerable<String> strings = new List<String>();
IEnumerable<Object> objects = strings;


Recommended Free Ebook
Similar Articles