Page 3 of 3
What's the point?
Now you understand the idea of covariance and contravariance you might be wondering what the point is?
The answer is that it is all a matter of when a language, or a language implementation, should, or could, allow automatic type conversion.
As array construction is covariant it is fairly safe to treat and array of B as being an array of A without needing an explicit type conversion. In many languages you can indeed write:
string x=new string;
without the need for a cast and this is because object>string implies object>string.
For a more involved example consider the C# delegate.
A delegate is a type that can wrap a function with a specific signature. A delegate accepts a function as input parameter and returns a delegate type that wraps it.
Consider a delegate
and two functions which only differ in the type of their input parameter then A>B implies by contravariance:
functionA(A param)<functionB(B param);
Hence functionA can be used anywhere functionB can and so it should be safe to use Mydelegate to wrap an instance of a function that has parameters that are less derived than it.
By a similar argument a delegate:
delegate Mydelegate(A function);
which wraps a function which returns and A should be quite safe wrapping a function that returns a B as A>B implies by covariance that A function>B function and so you can use a B function anywhere you can use an A function. Thus delegates are covariance in the return parameter of the function they wrap and so can safely wrap a function that returns a more derived result.
Is it worth it?
Is it worth introducing what appear to be complex ideas like covariance and contravariance?
In practical work probably not as the whole thing is usually simple enough to work out from first principles.
For example, can an array of strings be used in place of an array of objects? Well yes obviously as a string is just an object with additional properties and methods so it can always be treated as an object.
So if you want to just think about type conversion and substitution rules from first principles that's fine. If you want to dress the idea up in the terms covariance and contravariance that's fine to. However, the academic terms do have the disadvantage that it's easy to miss practical concerns.
Languages have to be pragmatic and this means they don't always obey the substitution principle.
For example an double can always be used where an integer can be used - e.g. write 1.0 in place of 1 but in practice it it rare for a language to define a double as a derived type of int.
For another example, consider the array of string types. If this is cast to an array of object types then this works for all read access but write access often fails. For example, try
string mystring=new string;
in most languages this will fail because the underlying type of the object element is string and not object but by the substitution principle it should work. In this case it is a result of inheritance not being implemented fully, i.e. an array of strings isn't really an array of objects with some additional properties.
In practice programming is more complicated and messy that pure theory allows.
Look out for a follow-on article that explains covariance and contravariance in C# 4.0.
If you would like to be informed about new articles on I Programmer you can either follow us on Twitter, on Facebook , on Digg or you can subscribe to our weekly newsletter.
Casting – the escape from strong typing
Introduction to Delegates