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 and contravariance are terms that refer to the ability to use a more derived type (more specific) or a less derived type (less specific) than originally specified. Generic type parameters support covariance and contravariance to provide greater flexibility in assigning and using generic types.
In C#, covariance and contravariance enable implicit reference conversion for array types, delegate types, and generic type arguments. Covariance preserves assignment compatibility and contravariance reverses it.
This is called upcasting. Upcasting is done automatically, while downcasting must be manually done by the programmer, and i'm going to give my best to explain why is that so.
Covariance in C# Covariance enables you to pass a derived type where a base type is expected. Co-variance is like variance of the same kind. The base class and other derived classes are considered to be the same kind of class that adds extra functionalities to the base type.
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 withY
, thenF<X>
is assignment compatible withF<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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With