64

What is the difference between covariance and upcasting, or, more specifically, why are they given different names?

I've seen the following example referred to as 'upcasting':

string s = "hello";
object o = s;  //upcast to 'string' to 'object'

Whereas, the following I have seen called 'covariance':

string[] s = new string[100];
object[] o = s;

IEnumerable<string> ies = new List<string>();
IEnumerable<object> ieo = ies;

Now, to my untrained eye, covariance seems to be the same as upcasting, except that it refers the casting of collections. (And of a similar statement can be made regarding contravariance and downcasting).

Is it really that simple?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
James Wiseman
  • 29,946
  • 17
  • 95
  • 158
  • 12
    Jason's answer is pretty much spot on. For a more detailed explanation of the difference between covariance and assignment compatibility, see my article on the subject. http://blogs.msdn.com/b/ericlippert/archive/2009/11/30/what-s-the-difference-between-covariance-and-assignment-compatibility.aspx You are not alone; lots of people get these two confused. – Eric Lippert Jul 15 '11 at 14:08
  • @Eric Lippert: Cheers. Had I read that article before, i may never have posted this! Glad I did though. In truth, I was surprised there wasn't already a similar question like this on SO. – James Wiseman Jul 15 '11 at 14:19

6 Answers6

60

Now, to my untrained eye, covariance seems to be the same as upcasting, except that it refers the casting of collections. (And of a similar statement can be made regarding contravariance and downcasting).

Is it really that simple?

Covariance isn't about upcasting, although I can see why you think it's related.

Covariance is about the following very simple idea. Let's say you have a variable derivedSequence of type IEnumerable<Derived>. Let's say you have a variable baseSequence of type IEnumerable<Base>. Here, Derived derives from Base. Then, with covariance, the following is a legal assignment, and an implicit reference conversion occurs:

baseSequence = derivedSequence;

Note that this is not upcasting. It is not the case that IEnumerable<Derived> derives from IEnumerable<Base>. Rather, it is covariance that allows you to assign the value of the variable derivedSequence to the variable baseSequence. The idea is that variables of type Base can be assigned from objects of type Derived, and since IEnumerable<T> is covariant in its parameter, objects of type IEnumerable<Derived> can be assigned to variables of type IEnumerable<Base>.

Of course, I haven't yet really explained what covariance is. In general, covariance is about the following simple idea. Let's say you have a mapping F from types to types (I'll denote this mapping by F<T>; given a type T its image under the mapping F is F<T>.) Let's say that this mapping has the following very special property:

if X is assignment compatible with Y, then F<X> is assignment compatible with F<Y> as well.

In this case, we say that F is covariant in its parameter T. (Here, to say that "A is assignment compatible with B" where A and B are reference types means that instances of B can be stored in variables of type A.)

In our case, IEnumerable<T> in C# 4.0, an implicit reference conversion from instances of IEnumerable<Derived> to IEnumerable<Base> if Derived is derived from Base. The direction of assignment compatibility is preserved, and this is why we say that IEnumerable<T> is covariant in its type parameter.

Community
  • 1
  • 1
jason
  • 236,483
  • 35
  • 423
  • 525
19

Casting refers to changing the static type of objects and expressions.

Variance refers to the interchangeability or equivalence of types in certain situations (such as parameters, generics, and return types).

Jon
  • 428,835
  • 81
  • 738
  • 806
16

IEnumerable<string> is not derived from IEnumerable<object>, so the cast between them is not upcasting. IEnumerable is covariant in its type parameter and string is derived from object, so the cast is allowed.

antlersoft
  • 14,636
  • 4
  • 35
  • 55
  • Since this is proving such a popular question, I was going to expand on this answer-- but I'll refer to @Jason's very good answer. – antlersoft Jul 15 '11 at 13:53
8

The reason they are different concepts is that, unlike upcasting, covariance is not always allowed. It would have been easy for the designers of the type-system to make IList<Cat> be considered as "derived" from IList<Animal>, but then we run into problems:

IList<Cat> cats = new List<Cat>();
IList<Animal> animals = cats; 
animals.Add(new Dog()); //Uh oh!

If this were allowed, now our cats list would contain a Dog!

In contrast, the IEnumerable<T> interface has no way of adding elements, so this is perfectly valid (in C# 4.0):

IList<Cat> cats = new List<Cat>();
IEnumerable<Animal> animals = cats;
//There's no way to add things to an IEnumerable<Animal>, so here we are ok
BlueRaja - Danny Pflughoeft
  • 84,206
  • 33
  • 197
  • 283
1

Thie blog post below has a good explanation of it:

http://blogs.msdn.com/b/csharpfaq/archive/2010/02/16/covariance-and-contravariance-faq.aspx

Jon Egerton
  • 40,401
  • 11
  • 97
  • 129
0

From what I can gather covariance removes the need for explicit downcasting subsequent to a previous upcast. Typically if you upcast an object you can only access the base type methods and attributes, with covariance it seems you can imply the downcast by replacing lesser derived types with more derived types in the more derived class declaration.

ComeIn
  • 1,519
  • 17
  • 12