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
Update: Ooops. As it turned out, I mixed up variance and "assignment compatibility" in my initial answer. Edited the answer accordingly. Also I wrote a blog post that I hope should answer such questions better: Covariance and Contravariance FAQ
Answer: I guess the answer to your first question is that you don't have contravariance in this example:
bool Compare(Mammal mammal1, Mammal mammal2);
Mammal mammal1 = new Giraffe(); //covariant - no
Mammal mammal2 = new Dolphin(); //covariant - no
Compare(mammal1, mammal2); //covariant or contravariant? - neither
//or
Compare(new Giraffe(), new Dolphin()); //covariant or contravariant? - neither
Furthermore, you don't even have covariance here. What you have is called "assignment compatibility", which means that you can always assign an instance of a more derived type to an instance of a less derived type.
In C#, variance is supported for arrays, delegates, and generic interfaces. As Eric Lippert said in his blog post What's the difference between covariance and assignment compatibility? is that it's better to think about variance as "projection" of types.
Covariance is easier to understand, because it follows the assignment compatibility rules (array of a more derived type can be assigned to an array of a less derived type, "object[] objs = new string[10];"). Contravariance reverses these rules. For example, imagine that you could do something like "string[] strings = new object[10];". Of course, you can't do this because of obvious reasons. But that would be contravariance (but again, arrays are not contravariant, they support covariance only).
Here are the examples from MSDN that I hope will show you what contravariance really means (I own these documents now, so if you think something is unclear in the docs, feel free to give me feedback):
Using Variance in Interfaces for Generic Collections
Employee[] employees = new Employee[3];
// You can pass PersonComparer,
// which implements IEqualityComparer<Person>,
// although the method expects IEqualityComparer<Employee>.
IEnumerable<Employee> noduplicates =
employees.Distinct<Employee>(new PersonComparer());
Using Variance in Delegates
// Event hander that accepts a parameter of the EventArgs type.
private void MultiHandler(object sender, System.EventArgs e)
{
label1.Text = System.DateTime.Now.ToString();
}
public Form1()
{
InitializeComponent();
// You can use a method that has an EventArgs parameter,
// although the event expects the KeyEventArgs parameter.
this.button1.KeyDown += this.MultiHandler;
// You can use the same method
// for an event that expects the MouseEventArgs parameter.
this.button1.MouseClick += this.MultiHandler;
}
Using Variance for Func and Action Generic Delegates
static void AddToContacts(Person person)
{
// This method adds a Person object
// to a contact list.
}
// The Action delegate expects
// a method that has an Employee parameter,
// but you can assign it a method that has a Person parameter
// because Employee derives from Person.
Action<Employee> addEmployeeToContacts = AddToContacts;
Hope this helps.
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<Giraffe> giraffes = new[] { new Giraffe() };
IEnumerable<Animal> 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<T>
preserves the 'direction' of the type relationship since Giraffe < Animal
and IEnumerable<Giraffe> < IEnumerable<Animal>
(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<Animal> printAnimal = a => {System.Console.WriteLine(a.Name)};
Action<Giraffe> 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<Animal> < Action<Giraffe>
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<Mammal, Mammal, bool> compare = (m1, m2) => //whatever
Func<Giraffe, Dolphin, bool> 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());
Covariance and Contravariance are not things you can observe when instancing classes. Thus it is wrong to speak about one of them when looking at a simple class instantiation, like in your example:
Animal someAnimal = new Giraffe();
//covariant operation
These terms do not classify operations. The terms Covariance, Contravariance and Invariance describe the relationship between certain aspects of classes and their subclasses.
We generally regard the following aspects, when talking about Cov., Contrav. and Inv.:
Let us have a look at a few examples to get a better understanding of the terms.
class T
class T2 extends T
//Covariance: The return types of the method "method" have the same
//direction of inheritance as the classes A and B.
class A { T method() }
class B extends A { T2 method() }
//Contravariance: The parameter types of the method "method" have a
//direction of inheritance opposite to the one of the classes A and B.
class A { method(T2 t) }
class B { method(T t) }
In both cases, "method" gets overridden! Further, the above examples are the only legal occurrences of Cov. and Contrav. in object oriented languages.:
Let us have a look at some counter examples to better understand the above list:
//Covariance of return types: OK
class Monkey { Monkey clone() }
class Human extends Monkey { Human clone() }
Monkey m = new Human();
Monkey m2 = m.clone(); //You get a Human instance, which is ok,
//since a Human is-a Monkey.
//Contravariance of return types: NOT OK
class Fruit
class Orange extends Fruit
class KitchenRobot { Orange make() }
class Mixer extends KitchenRobot { Fruit make() }
KitchenRobot kr = new Mixer();
Orange o = kr.make(); //Orange expected, but got a fruit (too general!)
//Contravariance of parameter types: OK
class Food
class FastFood extends Food
class Person { eat(FastFood food) }
class FatPerson extends Person { eat(Food food) }
Person p = new FatPerson();
p.eat(new FastFood()); //No problem: FastFood is-a Food, which FatPerson eats.
//Covariance of parameter types: NOT OK
class Person { eat(Food food) }
class FatPerson extends Person { eat(FastFood food) }
Person p = new FatPerson();
p.eat(new Food()); //Oops! FastFood expected, but got Food (too general).
This topic is so sophisticated, that I could go on for a very long time. I advise you to check Cov. and Contrav. of Generics by yourself. Further, you need to know how dynamic binding works to fully understand the examples (which methods get exactly called).
The terms arose from the Liskov substitution principle, which defines necessary criteria for modelling a data type as a sub type of another one. You might also want to investigate it.
Look at it this way: If I have a function func that deals with Subtype Mammal, of the form Mammal m = Func(g(Mammal)), I can swap out Mammal with something that encompasses Mammal, which here is the Base Animal.
In terms of a sporting analogy to understand the below image, you can catch a ball with your bare hands like in Cricket, but it's also possible (and easier) to catch a ball using Baseball gloves.
What you see on the left is covariance, what you see inside the parameter part is contravariance.
You may wonder "Why is the left green curve bigger than the red curve? Isn't the Subtype which usually does more than the basetype supposed to be bigger?" Answer: No. The size of the bracket denotes the variety of objects allowed, like a Venn diagram. A Set of Mammal is smaller than Set Animal. Similarly, f(Mammal) is smaller than f(Animal) as it only supports a smaller set of objects. (i.e a function that handles Mammals won't handle all Animals, but a function that handles Animals can always handle a Mammal). Hence, the relationship is inverted as f(animal) can be passed in instead of f(mammal) thereby making it contravariant.
(Edited in response to comments)
This MSDN article on the topic described covariance and contravariance as it applies to matching a function to a delegate. A variable of the delegate type:
public delegate bool Compare(Giraffe giraffe, Dolphin dolphin);
could (because of contravariance) be populated with the function:
public bool Compare(Mammal mammal1, Mammal mammal2)
{
return String.Compare(mammal1.Name, mammal2.Name) == 0;
}
From my reading, it doesn't have to do with calling the function directly, but matching functions with delegates. I'm not sure that it can be boiled down to the level you demonstrate, with individual variables or object assignments being contravariant or covariant. But the assignment of a delegate uses contravariance or covariance in a way that makes sense to me according to the linked article. Because the delegate's signature contains more derived types than the actual instance, this is referred to as "contravariance", something separate from "covariance" in which a delegate's return type is less derived than the actual instance.