First of, I have read many explanations on SO and blogs about covariance and contravariance and a big thanks goes out to Eric Lippert for producing such a great series on Co
My understanding is that it is not subtype relationships which are co/contra-variant but rather operations (or projections) between those types (such as delegates and generics). Therefore:
Animal someAnimal = new Giraffe();
is not co-variant, but rather this is just assignment compatibility since the type Giraffe is 'smaller than' the type Animal. Co/contra-variance becomes an issue when you have some projection between these types, such as:
IEnumerable giraffes = new[] { new Giraffe() };
IEnumerable animals = giraffes;
This is not valid in C#3, however it should be possible since a sequence of giraffes is a sequence of animals. The projection T -> IEnumerable
preserves the 'direction' of the type relationship since Giraffe < Animal
and IEnumerable
(note that assignment requires that the type of the left-hand side is at least as wide as the right).
Contra-variance reverses the type relationship:
Action printAnimal = a => {System.Console.WriteLine(a.Name)};
Action printGiraffe = printAnimal;
This is also not legal in C#3, but it should be since any action taking an animal can cope with being passed a Giraffe. However, since Giraffe < Animal
and Action
the projection has reversed the type relationships. This is legal in C#4.
So to answer the questions in your example:
//the following are neither covariant or contravariant - since there is no projection this is just assignment compatibility
Mammal mammal1 = new Giraffe();
Mammal mammal2 = new Dolphin();
//compare is contravariant with respect to its arguments -
//the delegate assignment is legal in C#4 but not in C#3
Func compare = (m1, m2) => //whatever
Func c2 = compare;
//always invalid - right hand side must be smaller or equal to left hand side
Mammal mammal1 = new Animal();
//not valid for same reason - animal cannot be assigned to Mammal
Compare(new Animal(), new Dolphin());