Fabulous Adventures In Coding
Eric Lippert is a principal developer on the C# compiler team. Learn more about Eric.
I have been wanting for a long time to do a series of articles about covariance and contravariance (which I will shorten to “variance” for the rest of this series.)
I’ll start by defining some terms, then describe what variance features C# 2.0 and 3.0 already support today, and then discuss some ideas we are thinking about for hypothetical nonexistant future versions of C#.
As always, keep in mind that we have not even shipped C# 3.0 yet. Any of my musings on possible future additions to the language should be treated as playful hypotheses, rather than announcements of a commitment to ship any product with any feature whatsoever.
Today: what do we mean by “covariance” and “contravariance”?
The first thing to understand is that for any two types T and U, exactly one of the following statements is true:
For example, consider a type hierarchy consisting of Animal, Mammal, Reptile, Giraffe, Tiger, Snake and Turtle, with the obvious relationships. ( Mammal is a subclass of Animal, etc.) Mammal is a bigger type than Giraffe and smaller than Animal, and obviously equal to Mammal. But Mammal is neither bigger than, smaller than, nor equal to Reptile, it’s just different.
Why is this relevant? Suppose you have a variable, that is, a storage location. Storage locations in C# all have a type associated with them. At runtime you can store an object which is an instance of an equal or smaller type in that storage location. That is, a variable of type Mammal can have an instance of Giraffe stored in it, but not a Turtle.
This idea of storing an object in a typed location is a specific example of a more general principle called the “substitution principle”. That is, in many contexts you can often substitute an instance of a “smaller” type for a “larger” type.
Now we can talk about variance. Consider an “operation” which manipulates types. If the results of the operation applied to any T and U always results in two types T’ and U’ with the same relationship as T and U, then the operation is said to be “covariant”. If the operation reverses bigness and smallness on its results but keeps equality and unrelatedness the same then the operation is said to be “contravariant”.
That’s totally highfalutin and probably not very clear. Next time we’ll look at how C# 3 implements variance at present.
I have been wanting for a long time to do a series of articles about covariance and contravariance (which I will shorten to “variance” for the rest of this series.) I’ll start by defining some terms, then describe what variance features C# 2.0 and 3.
So nicely step by step blogged by Eric Lippert for "Covariance and Contravariance" as "Fabulous
You defined "smaller" as "assignment-compatible in the CLR type system" which includes "is a subtype of" relationship. I always thought of assignment-compatibility as: the right hand side value (RHS) must be of the same type or a subtype of the variable on the left hand side (LHS). Because of this, I do not get the definition of "smaller". Can you give me an example where the RHS is neither of the same type nor a subtype of the LHS? It would greatly help me understand variance. Thanks.
It would be helpful if you would make it more clear what inherits what.
I have been trying to solve a problem for a while, and thought Covariance was the answer, but it still does not get me there:
I want to be able to change the type that the ManagerFactory passes in on the fly:
(i.e. if paymentCode=1 then var managerFactory = new ManagerFactory<CheckMoneyOrderPaymentManager>();
if paymentCode=2 then var managerFactory = new ManagerFactory<ACHPaymentManager>();, etc)
var managerFactory = new ManagerFactory<CheckMoneyOrderPaymentManager>();
var vCheckMoneyOrderPayment =managerFactory.GetPaymentManager(Extended.GetPaymentManagerTypes(paymentTypeId));
My ManagerFactory is:
public class ManagerFactory<T> : IManagerFactory<T> where T : new()
public T GetPaymentManager(Extended.PaymentManagerTypes paymentManagerTypes)
return new T();
Hi Eric, you say: At runtime you can store an object which is an instance of an equal or smaller type in that storage location. That is, a variable of type Mammal can have an instance of Giraffe stored in it.
Isnt Mammal the "smaller" object?
I like your use of the words small and large to describe relationships!