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?
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
derivedSequenceof typeIEnumerable<Derived>. Let’s say you have a variablebaseSequenceof typeIEnumerable<Base>. Here,Derivedderives fromBase. Then, with covariance, the following is a legal assignment, and an implicit reference conversion occurs:Note that this is not upcasting. It is not the case that
IEnumerable<Derived>derives fromIEnumerable<Base>. Rather, it is covariance that allows you to assign the value of the variablederivedSequenceto the variablebaseSequence. The idea is that variables of typeBasecan be assigned from objects of typeDerived, and sinceIEnumerable<T>is covariant in its parameter, objects of typeIEnumerable<Derived>can be assigned to variables of typeIEnumerable<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
Ffrom types to types (I’ll denote this mapping byF<T>; given a typeTits image under the mappingFisF<T>.) Let’s say that this mapping has the following very special property:In this case, we say that
Fis covariant in its parameterT. (Here, to say that "Ais assignment compatible withB" whereAandBare reference types means that instances ofBcan be stored in variables of typeA.)In our case,
IEnumerable<T>in C# 4.0, an implicit reference conversion from instances ofIEnumerable<Derived>toIEnumerable<Base>ifDerivedis derived fromBase. The direction of assignment compatibility is preserved, and this is why we say thatIEnumerable<T>is covariant in its type parameter.