Difference between covariance and upcasting

C#CastingCovariance

C# Problem Overview


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?

C# Solutions


Solution 1 - C#

>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.

Solution 2 - C#

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).

Solution 3 - C#

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.

Solution 4 - C#

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

Solution 5 - C#

Solution 6 - C#

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.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionJames WisemanView Question on Stackoverflow
Solution 1 - C#jasonView Answer on Stackoverflow
Solution 2 - C#JonView Answer on Stackoverflow
Solution 3 - C#antlersoftView Answer on Stackoverflow
Solution 4 - C#BlueRaja - Danny PflughoeftView Answer on Stackoverflow
Solution 5 - C#Jon EgertonView Answer on Stackoverflow
Solution 6 - C#ComeInView Answer on Stackoverflow