Contravariance explained
Solution 1:
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.
Solution 2:
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.
- Covariance
- means that an aspect changes similar to the direction of inheritance.
- Contravariance
- means that an aspect changes opposite to the direction of inheritance.
- Invariance
- means that an aspect does not change from a class to its sub class(es).
We generally regard the following aspects, when talking about Cov., Contrav. and Inv.:
- Methods
- Parameter types
- Return types
- Other signature related aspects like thrown exceptions.
- Generics
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.:
- Covariance - Return types and exception throw statements
- Contravariance - Input parameters
- Invariance - Input and Output parameters
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.
Solution 3:
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());