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.
// Contravariance
interface IGobbler<in T> {
void gobble(T t);
}
// Since a QuadrupedGobbler can gobble any four-footed
// creature, it is OK to treat it as a donkey gobbler.
IGobbler<Donkey> dg = new QuadrupedGobbler();
dg.gobble(MyDonkey());
// Covariance
interface ISpewer<out T> {
T spew();
}
// A MouseSpewer obviously spews rodents (all mice are
// rodents), so we can treat it as a rodent spewer.
ISpewer<Rodent> rs = new MouseSpewer();
Rodent r = rs.spew();
For completeness…
// Invariance
interface IHat<T> {
void hide(T t);
T pull();
}
// A RabbitHat…
IHat<Rabbit> rHat = RabbitHat();
// …cannot be treated covariantly as a mammal hat…
IHat<Mammal> mHat = rHat; // Compiler error
// …because…
mHat.hide(new Dolphin()); // Hide a dolphin in a rabbit hat??
// It also cannot be treated contravariantly as a cottontail hat…
IHat<CottonTail> cHat = rHat; // Compiler error
// …because…
rHat.hide(new MarshRabbit());
cHat.pull(); // Pull a marsh rabbit out of a cottontail hat??
Best Answer
All of the above.
At heart, these terms describe how the subtype relation is affected by type transformations. That is, if
A
andB
are types,f
is a type transformation, and ≤ the subtype relation (i.e.A ≤ B
means thatA
is a subtype ofB
), we havef
is covariant ifA ≤ B
implies thatf(A) ≤ f(B)
f
is contravariant ifA ≤ B
implies thatf(B) ≤ f(A)
f
is invariant if neither of the above holdsLet's consider an example. Let
f(A) = List<A>
whereList
is declared byIs
f
covariant, contravariant, or invariant? Covariant would mean that aList<String>
is a subtype ofList<Object>
, contravariant that aList<Object>
is a subtype ofList<String>
and invariant that neither is a subtype of the other, i.e.List<String>
andList<Object>
are inconvertible types. In Java, the latter is true, we say (somewhat informally) that generics are invariant.Another example. Let
f(A) = A[]
. Isf
covariant, contravariant, or invariant? That is, is String[] a subtype of Object[], Object[] a subtype of String[], or is neither a subtype of the other? (Answer: In Java, arrays are covariant)This was still rather abstract. To make it more concrete, let's look at which operations in Java are defined in terms of the subtype relation. The simplest example is assignment. The statement
will compile only if
typeof(y) ≤ typeof(x)
. That is, we have just learned that the statementswill not compile in Java, but
will.
Another example where the subtype relation matters is a method invocation expression:
Informally speaking, this statement is evaluated by assigning the value of
a
to the method's first parameter, then executing the body of the method, and then assigning the methods return value toresult
. Like the plain assignment in the last example, the "right hand side" must be a subtype of the "left hand side", i.e. this statement can only be valid iftypeof(a) ≤ typeof(parameter(method))
andreturntype(method) ≤ typeof(result)
. That is, if method is declared by:none of the following expressions will compile:
but
will.
Another example where subtyping matters is overriding. Consider:
where
Informally, the runtime will rewrite this to:
For the marked line to compile, the method parameter of the overriding method must be a supertype of the method parameter of the overridden method, and the return type a subtype of the overridden method's one. Formally speaking,
f(A) = parametertype(method asdeclaredin(A))
must at least be contravariant, and iff(A) = returntype(method asdeclaredin(A))
must at least be covariant.Note the "at least" above. Those are minimum requirements any reasonable statically type safe object oriented programming language will enforce, but a programming language may elect to be more strict. In the case of Java 1.4, parameter types and method return types must be identical (except for type erasure) when overriding methods, i.e.
parametertype(method asdeclaredin(A)) = parametertype(method asdeclaredin(B))
when overriding. Since Java 1.5, covariant return types are permitted when overriding, i.e. the following will compile in Java 1.5, but not in Java 1.4:I hope I covered everything - or rather, scratched the surface. Still I hope it will help to understand the abstract, but important concept of type variance.