Understanding Covariance and Contravariance
I believe in the idea that everyone is given gifts in different proportions. My gift was the gift of knowledge. I can learn things or figure them out pretty quickly except for instructions for assemble furniture. That is why it is especially frustrating for me when I don't intuitively grasp a concept. It doesn't happen that often, especially where programming concepts are concerned, but it does happen. The most recent I am not sure I get it moment occurred when I was trying to figure out what the heck contravariance and covariance in .NET 4.0 are for. There are a lot of posts and reposts that attempt to describe these abstruse concepts, but a lot of that information adds to the confusion. Fortunately for me Eric Lipper and Charlie Calvert and their respective posts on the subject helped set me straight. (Of course, I am not sure I get everything about variance and any mistakes are mine, but I have tried to boil it down here.)
According to the Eric Lippert the math is based on Category theory. I took a whole lot of math but not category theory. Eric does a great job explaining the math principles in his blog post ";What's the difference between covariance and assignment compatibility?"; here http://blogs.msdn.com/b/ericlippert/archive/2009/11/30/what-s-the-difference-between-covariance-and-assignment-compatibility.aspx, so I won't repeat that information. Instead let's skip to a brief explanation as it applies to .NET.
Assignment compatibility is that aspect of programming that lets you have a parent class like Music and a child class like Rap, declares a Music type and assign it to an instance of Rap.
class Music{} class Rap : Music{} Music Music = new Rap();
Without assignment compatibility polymorphism wouldn't work and consequently OO programming wouldn't work very well. Covariance preserves assignment compatibility and contravariance reverses it. This doesn't help me very much. I don't know about you. If you check the MSDN help it indicates that covariance is the ability to assign a narrower type-read child-to a wider type-read parent, and contravariance permits assigning a wider type to a narrower type. Very simplistically think covariance supports Music music = new Rap() and Rap rap = new Music(). Of course, Eric in the aforementioned blog says that that is not all covariance means; that the simple description has more to do with assignment compatibility. Eric Lippert and Charlie Calvert's many blogs on the subject go into a lot of detail, but I want to boil it down some more so will make things as simple as I understand them and will use just one instance where they appear, generic delegates.
Assignment Compatibility
Assignment compatibility means when it is okay to assign a more derived type-child-to a parent type. Suppose I define a generic delegate delegate T MyFunc
This is all copacetic and works as one intuitively expects. Where things go haywire is if you attempt to define a second instance of the generic delegate where T is an Music and assign the generic Rap delegate to the second delegate:
What the heck? Rap is a kind of Music (to some people, including me) and one would think this would work. It didn't work in earlier versions of .NET, but it does work in .NET 4.0 and it is an example of covariance. Simply change the delegate parameter from T to out T and covariance is supported. Here is the revised code.
Notice that MyFunc now defines T as an out parameter. Adding out to the generic parameter makes T covariant and now the intuitive assignment to the wider Music delegate from the narrower Rap delegate works.
Contravariance is the ability (roughly) to reverse the order of assignment above. One might intuitively think that it is acceptable to assign a generic delegate that performs on operation on a Music object to an operation that performs an operation on a Rap object, but pre-.NET 4.0 this wouldn't worth either. Suppose you have a generic delegate
and initialize it to
and then try to assign it a delegate that acts on a Rap object
as defined this won't compile. However, if you change the generic parameter T to an in parameter then the code compiles and works as expected. Here is the solution demonstrating contravariance.
Suppose further that you defined a class Classical that inherits from Music. The compiler does a good job checking to make sure that the actual contravariant assignment makes sense. As a result you could not assign a generic delegate that accepts a Classical object parameter to a delegate that accepts a Rap generic parameter.
For covariance think the out modifier, and for contravariance think the in modifier. The net effect is that assignment compatibility is extended and preserved where it feels more intuitive and you can reverse the assignment order when it makes sense to do so.What the heck are covariance and contravariance? In a simple sense they preserve assignment compatibility in a more intuitive manner between parent and child relationships.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace VarianceDemo
{
class Program
{
delegate T MyFunc<T>();
static void Main(string[] args)
{
// assignment compatibility
MyFunc<Rap> returnRap = () => new Rap();
Music Music = returnRap();
Console.ReadLine();
}
}
class Music{}
class Rap : Music{}
}
MyFunc<Music> returnMusic = returnRap;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace VarianceDemo
{
class Program
{
delegate T MyFunc<out T>();
static void Main(string[] args)
{
//covariance
// assignment compatibility
MyFunc<Rap> returnRap = () => new Rap();
Music Music = returnRap();
MyFunc<Music> returnMusic = returnRap;
}
}
class Music{}
class Rap : Music{}
}
delegate void MyAction<T>(T t);
MyAction<Music> printMusic = (a)=>Console.WriteLine(a);
MyAction<Rap> printRap = printMusic;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace VarianceDemo
{
class Program
{
delegate void MyAction<in T>(T t);
static void Main(string[] args)
{
// contravariance
MyAction<Music> printMusic = (a)=>Console.WriteLine(a);
MyAction<Rap> printRap = printMusic;
Console.ReadLine();
}
}
class Music{}
class Rap : Music{}
}
// this is invalid
MyAction<Classical> printClassical = (a) => Console.WriteLine(a);
MyAction<Rap> printRap2 = printClassical;

