Search
Close this search box.

Covariance, contravariance and invariance in C# language

Introduction

The newest version of C# language – 4.0 – will be shipped with a couple of interesting features.  One of them is to allow covariance and contravariance on parameterized delegate and interface types. Sounds very mysteriously, isn`t it?

There are many great developers that didn`t hear about any kind of
“-variance” (although they are using it unwittingly very often). This has much to do with inheritance – in fact many people think that this is simply just “another thing” offered by generalization. So why bother for this extra knowledge? Because C# language`s operators are sometimes covariant, sometimes contravariant and sometimes invariant. Knowing principles of these is imperative in order to avoid compile errors, nasty runtime exceptions and to be able to use smarter programming techniques.

This article provides a brief description of co- and contravariance in C# programming language. The first part introduces some key theoretical concepts and notions. The second part focuses on exploitation of this technique in C# language.

Theory

Let`s begin with the most boring (but necessary) part – theory. An operator between types is said to be covariant if it orders these types from more specific to more general ones. Similarly an operator between types is said to be contravariant if it orders them in the reversed order. Whenever neither of these conditions is met, an operator is said to be invariant.

Keeping in mind that these definitions probably didn`t explain much to you (however the opposite may be true) let`s get to some real-live examples.

Usage

These concepts are commonly used in many object-oriented programming languages (including C#, C++ and Java) to allow high degree of flexibility. This flexibility can be achieved by replacing the original type of data by type that derives from it (contravariance) or by type from which the given type derives (covariance).

Both of these will be shown on the example. Suppose we have the following class model implemented in our application:

and that the following method has been defined:

public Class2 Method(Class2 argument)
{
      return null;
}

Covariance

In C# return value of every method is covariant, while all arguments are contravariant. This means that the value returned by this method may be pointed by a reference of type C2, but also by reference of any type from which C2 derives (directly or not). As a result both of the following assignments are correct:

Class2 = Method(null);
Class1 = Method(null);

Assigning return value of this method to a reference of type Class3 will result in compile error, because the return value operator is not contravariant.

Contravariance

However all arguments of every method are contravariant (but not covariant). This means that instead of passing the object of type required by the method`s signature you are also allowed to pass object of every type that derives from it. So let`s get back to the previous example. Both of the following lines are proper:

Method(new Class2());
Method(new Class3());

Similarly to previous example, calling this method with an argument of type Class1 will get you compile error as method`s arguments are not covariant.

Collections

Collections are quite noteworthy case regarding co- and contravariance because they are treated differently. Type safety forces them to be invariant. This means that only object of type int[] (precisely) may be assigned to the following reference:

int[] array;

Seems quite harsh, huh? Indeed it is. That`s why Microsoft allowed all arrays of reference types to be covariant. This means that (getting back to previous example) you are allowed to make this assignment:

Class1[] generalizedArray;
Class2[] specializedArray = new Class2[]{new Class2()};
generalizedArray = specializedArray;

Seems comfortable? It really is, but this solution has a major drawback as well. Compiler won`t catch the following error:

generalizedArray[0] = new C1();

Compiler won`t even issue a warning. You will get a nasty ArrayTypeMismatchException at runtime instead. Quite painfull.

Conclusion

This article introduced elementary notions and ideas that stand behind covariance, contravariance and invariance. Usage of these in C# programming language along with a couple of simple examples has also been presented.

Co- and contravariance are interesting concepts. Having at least some basic knowledge about them could prove to be very useful, especially nowadays with C# 4.0 being at the door, as it will use them quite heavily.

This article is part of the GWB Archives. Original Author: Marcin Kasprzykowski

Related Posts