Categories
c# covariance

still confused about covariance and contravariance & in/out

64

ok i read a bit on this topic on stackoverflow, watched this & this, but still a bit confused about co/contra-variance.

from here

Covariance allows a “bigger” (less
specific) type to be substituted in an
API where the original type is only
used in an “output” position (e.g. as
a return value). Contravariance allows
a “smaller” (more specific) type to be
substituted in an API where the
original type is only used in an
“input” position.

i know it has to do with type safety.

about the in/out thing. can i say i use in when i need to write to it, and out when its read only. and in means contra-variance, out co-variance. but from the explanation above…

and here

For example, a List<Banana> can’t be
treated as a List<Fruit> because
list.Add(new Apple()) is valid for
List but not for List<Banana>.

so shouldn’t it be, if i were to use in/ am going to write to the object, it must be bigger more generic.

i know this question has been asked but still very confused.

3

  • 2

    I am confused as well, After reading up on it at Wikipedia I feel like I need to take real analysis 1&2, followed by measure theory, vector spaces, and finally categorization theory before I get a good intuitive feel for it. Alternatively, if someone posts a bunch of examples, I can probably cut down the learning time from 5 semesters to 1 month.

    Aug 10, 2010 at 2:40

  • 1

    Please don’t abbreviate here. The idea is to write in English, not to use things like “abt” and “abit”.

    Aug 10, 2010 at 3:48

62

Both covariance and contravariance in C# 4.0 refer to the ability of using a derived class instead of base class. The in/out keywords are compiler hints to indicate whether or not the type parameters will be used for input and output.

Covariance

Covariance in C# 4.0 is aided by out keyword and it means that a generic type using a derived class of the out type parameter is OK. Hence

IEnumerable<Fruit> fruit = new List<Apple>();

Since Apple is a Fruit, List<Apple> can be safely used as IEnumerable<Fruit>

Contravariance

Contravariance is the in keyword and it denotes input types, usually in delegates. The principle is the same, it means that the delegate can accept more derived class.

public delegate void Func<in T>(T param);

This means that if we have a Func<Fruit>, it can be converted to Func<Apple>.

Func<Fruit> fruitFunc = (fruit)=>{};
Func<Apple> appleFunc = fruitFunc;

Why are they called co/contravariance if they are basically the same thing?

Because even though the principle is the same, safe casting from derived to base, when used on the input types, we can safely cast a less derived type (Func<Fruit>) to a more derived type (Func<Apple>), which makes sense, since any function that takes Fruit, can also take Apple.

2

  • 1

    The delegate you describe should be public delegate void Func<in T>(T param); right? 😉

    Aug 10, 2010 at 4:14

  • 13

    Better to call it Action. Func is confusing there 🙂

    – nawfal

    Jul 7, 2014 at 6:22

62

Both covariance and contravariance in C# 4.0 refer to the ability of using a derived class instead of base class. The in/out keywords are compiler hints to indicate whether or not the type parameters will be used for input and output.

Covariance

Covariance in C# 4.0 is aided by out keyword and it means that a generic type using a derived class of the out type parameter is OK. Hence

IEnumerable<Fruit> fruit = new List<Apple>();

Since Apple is a Fruit, List<Apple> can be safely used as IEnumerable<Fruit>

Contravariance

Contravariance is the in keyword and it denotes input types, usually in delegates. The principle is the same, it means that the delegate can accept more derived class.

public delegate void Func<in T>(T param);

This means that if we have a Func<Fruit>, it can be converted to Func<Apple>.

Func<Fruit> fruitFunc = (fruit)=>{};
Func<Apple> appleFunc = fruitFunc;

Why are they called co/contravariance if they are basically the same thing?

Because even though the principle is the same, safe casting from derived to base, when used on the input types, we can safely cast a less derived type (Func<Fruit>) to a more derived type (Func<Apple>), which makes sense, since any function that takes Fruit, can also take Apple.

2

  • 1

    The delegate you describe should be public delegate void Func<in T>(T param); right? 😉

    Aug 10, 2010 at 4:14

  • 13

    Better to call it Action. Func is confusing there 🙂

    – nawfal

    Jul 7, 2014 at 6:22

34

Let me share my take on this topic.

Disclaimer: ignore null assignments, I’m using them to keep the code relatively short and they are just enough to see what compiler wants to tell us.

Let’s start with a hierarchy of classes:

class Animal { }

class Mammal : Animal { }

class Dog : Mammal { }

Now define some interfaces, to illustrate what in and out generic modifiers actually do:

interface IInvariant<T>
{
    T Get(); // ok, an invariant type can be both put into and returned
    void Set(T t); // ok, an invariant type can be both put into and returned
}

interface IContravariant<in T>
{
    //T Get(); // compilation error, cannot return a contravariant type
    void Set(T t); // ok, a contravariant type can only be **put into** our class (hence "in")
}

interface ICovariant<out T>
{
    T Get(); // ok, a covariant type can only be **returned** from our class (hence "out")
    //void Set(T t); // compilation error, cannot put a covariant type into our class
}

Ok, so why bother using interfaces with in and out modifiers if they restrict us? Let’s see:


Invariance

Lets start with invariance (no in, no out modifiers)

Invariance experiment

Consider IInvariant<Mammal>

  • IInvariant<Mammal>.Get() – returns a Mammal
  • IInvariant<Mammal>.Set(Mammal) – accepts a Mammal

What if we try: IInvariant<Mammal> invariantMammal = (IInvariant<Animal>)null?

  • Whoever calls IInvariant<Mammal>.Get() expects a Mammal, but IInvariant<Animal>.Get() – returns an Animal. Not every Animal is a Mammal so it’s incompatible.
  • Whoever calls IInvariant<Mammal>.Set(Mammal) expects that a Mammal can be passed. Since IInvariant<Animal>.Set(Animal) accepts any Animal (including Mammal), it’s compatible
  • CONCLUSION: such assignment is incompatible

And what if we try: IInvariant<Mammal> invariantMammal = (IInvariant<Dog>)null?

  • Whoever calls IInvariant<Mammal>.Get() expects a Mammal, IInvariant<Dog>.Get() – returns a Dog, every Dog is a Mammal, so it’s compatible.
  • Whoever calls IInvariant<Mammal>.Set(Mammal) expects that a Mammal can be passed. Since IInvariant<Dog>.Set(Dog) accepts only Dogs (and not every Mammal as a Dog), it’s incompatible.
  • CONCLUSION: such assignment is incompatible

Let’s check if we’re right

IInvariant<Animal> invariantAnimal1 = (IInvariant<Animal>)null; // ok
IInvariant<Animal> invariantAnimal2 = (IInvariant<Mammal>)null; // compilation error
IInvariant<Animal> invariantAnimal3 = (IInvariant<Dog>)null; // compilation error

IInvariant<Mammal> invariantMammal1 = (IInvariant<Animal>)null; // compilation error
IInvariant<Mammal> invariantMammal2 = (IInvariant<Mammal>)null; // ok
IInvariant<Mammal> invariantMammal3 = (IInvariant<Dog>)null; // compilation error

IInvariant<Dog> invariantDog1 = (IInvariant<Animal>)null; // compilation error
IInvariant<Dog> invariantDog2 = (IInvariant<Mammal>)null; // compilation error
IInvariant<Dog> invariantDog3 = (IInvariant<Dog>)null; // ok

THIS ONE IS IMPORTANT: It’s worth noticing that depending on whether the generic type parameter is higher or lower in class hierarchy, the generic types themselves are incompatible for different reasons.

Ok, so let’s find out how could we exploit it.


Covariance (out)

You have covariance when you use out generic modifier (see above)

If our type looks like: ICovariant<Mammal>, it declares 2 things:

  • Some of my methods return a Mammal (hence out generic modifier) – this is boring
  • None of my methods accept a Mammal – this is interesting though, because this is the actual restriction imposed by the out generic modifier

How can we benefit from out modifier restrictions? Look back at the results of the “Invariance experiment” above. Now try to see what happens when make the same experiment for covariance?

Covariance experiment

What if we try: ICovariant<Mammal> covariantMammal = (ICovariant<Animal>)null?

  • Whoever calls ICovariant<Mammal>.Get() expects a Mammal, but ICovariant<Animal>.Get() – returns an Animal. Not every Animal is a Mammal so it’s incompatible.
  • ICovariant.Set(Mammal) – this is no longer an issue thanks to the out modifier restrictions!
  • CONCLUSION such assignment is incompatible

And what if we try: ICovariant<Mammal> covariantMammal = (ICovariant<Dog>)null?

  • Whoever calls ICovariant<Mammal>.Get() expects a Mammal, ICovariant<Dog>.Get() – returns a Dog, every Dog is a Mammal, so it’s compatible.
  • ICovariant.Set(Mammal) – this is no longer an issue thanks to the out modifier restrictions!
  • CONCLUSION such assignment is COMPATIBLE

Let’s confirm it with the code:

ICovariant<Animal> covariantAnimal1 = (ICovariant<Animal>)null; // ok
ICovariant<Animal> covariantAnimal2 = (ICovariant<Mammal>)null; // ok!!!
ICovariant<Animal> covariantAnimal3 = (ICovariant<Dog>)null; // ok!!!

ICovariant<Mammal> covariantMammal1 = (ICovariant<Animal>)null; // compilation error
ICovariant<Mammal> covariantMammal2 = (ICovariant<Mammal>)null; // ok
ICovariant<Mammal> covariantMammal3 = (ICovariant<Dog>)null; // ok!!!

ICovariant<Dog> covariantDog1 = (ICovariant<Animal>)null; // compilation error
ICovariant<Dog> covariantDog2 = (ICovariant<Mammal>)null; // compilation error
ICovariant<Dog> covariantDog3 = (ICovariant<Dog>)null; // ok

Contravariance (in)

You have contravariance when you use in generic modifier (see above)

If our type looks like: IContravariant<Mammal>, it declares 2 things:

  • Some of my methods accept a Mammal (hence in generic modifier) – this is boring
  • None of my methods return a Mammal – this is interesting though, because this is the actual restriction imposed by the in generic modifier

Contravariance experiment

What if we try: IContravariant<Mammal> contravariantMammal = (IContravariant<Animal>)null?

  • IContravariant<Mammal>.Get() – this is no longer an issue thanks to the in modifier restrictions!
  • Whoever calls IContravariant<Mammal>.Set(Mammal) expects that a Mammal can be passed. Since IContravariant<Animal>.Set(Animal) accepts any Animal (including Mammal), it’s compatible
  • CONCLUSION: such assignment is COMPATIBLE

And what if we try: IContravariant<Mammal> contravariantMammal = (IContravariant<Dog>)null?

  • IContravariant<Mammal>.Get() – this is no longer an issue thanks to the in modifier restrictions!
  • Whoever calls IContravariant<Mammal>.Set(Mammal) expects that a Mammal can be passed. Since IContravariant<Dog>.Set(Dog) accepts only Dogs (and not every Mammal as a Dog), it’s incompatible.
  • CONCLUSION: such assignment is incompatible

Let’s confirm it with the code:

IContravariant<Animal> contravariantAnimal1 = (IContravariant<Animal>)null; // ok
IContravariant<Animal> contravariantAnimal2 = (IContravariant<Mammal>)null; // compilation error
IContravariant<Animal> contravariantAnimal3 = (IContravariant<Dog>)null; // compilation error

IContravariant<Mammal> contravariantMammal1 = (IContravariant<Animal>)null; // ok!!!
IContravariant<Mammal> contravariantMammal2 = (IContravariant<Mammal>)null; // ok
IContravariant<Mammal> contravariantMammal3 = (IContravariant<Dog>)null; // compilation error

IContravariant<Dog> contravariantDog1 = (IContravariant<Animal>)null; // ok!!!
IContravariant<Dog> contravariantDog2 = (IContravariant<Mammal>)null; // ok!!!
IContravariant<Dog> contravariantDog3 = (IContravariant<Dog>)null; // ok

BTW, this feels a bit counterintuitive, doesn’t it?

// obvious
Animal animal = (Dog)null; // ok
Dog dog = (Animal)null; // compilation error, not every Animal is a Dog

// but this looks like the other way around
IContravariant<Animal> contravariantAnimal = (IContravariant<Dog>) null; // compilation error
IContravariant<Dog> contravariantDog = (IContravariant<Animal>) null; // ok

Why not both?

So can we use both in and out generic modifiers? – obviously not.

Why? Look back at what restrictions do in and out modifiers impose. If we wanted to make our generic type parameter both covariant and contravariant, we would basically say:

  • None of the methods of our interface returns T
  • None of the methods of our interface accepts T

Which would essentially make our generic interface non-generic.

How to remember it?

You can use my tricks 🙂

  1. “covariant” is shorter than “contravaraint” and this opposite to the lengths of their modifiers (“out” and “in” respectively)
  2. contravaraint is a little counterintuitive (see the example above)

3

  • 2

    I always have problem getting code paradigms explained in abstract grammar, I tend to trail off in there. So your explanation is a fresh approach and therefore the first one I actually understood full and through, it’s perfect! Thank you! You should write books!

    – bradbury

    Nov 15, 2018 at 20:53


  • Great explanation!

    Aug 26, 2020 at 14:28

  • 1

    One of a good example of – “how to progressively explain complex concepts?”. @andrzej you must be a great story teller 🙂

    Jan 17, 2021 at 17:24