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 mysterious, 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 about 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 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 in the example. Suppose we have the following class model...
... 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 the 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 cases 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 the previous example) you are allowed to make this assignment:
Class1[] generalizedArray;Class2[] specializedArray = new Class2[]{new Class2()};
Seems comfortable? It really is, but this solution has a major drawback as well. The compiler won't catch the following error:
generalizedArray[0] = new C1();
The compiler won't even issue a warning. You will get a nasty ArrayTypeMismatchException
at runtime instead. Quite painful.
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.
History
- 23rd July, 2009: Initial post