LeopardSkinPillBoxHat LeopardSkinPillBoxHat - 2 months ago 17
C# Question

Generic covariance compile time safety checks

The reason why

List<T>
isn't covariant on
T
, while
IEnumerable<T>
is covariant on
T
is often illustrated by an example like this.

Given the following classes:

public class Fruit
{
}

public class Apple : Fruit
{
}

public class Banana : Fruit
{
}


The following is permitted:

public void Permitted()
{
IEnumerable<Fruit> bananas = new List<Banana>
{
new Banana(),
new Banana(),
new Banana(),
};

foreach (Fruit banana in bananas)
{
// This is all good, because a banana "is a" fruit and
// we can treat it as such.
}
}


The following is disallowed:

public void Disallowed()
{
// Compiler rejects this!
List<Fruit> bananas = new List<Banana>
{
new Banana(),
new Banana(),
new Banana(),
};

// ...Otherwise we can add an apple to a list containing bananas
bananas.Add(new Apple());
}


However, we can still achieve this by doing the following:

public void Loophole()
{
// Compiler is happy again
IEnumerable<Fruit> bananas = new List<Banana>
{
new Banana(),
new Banana(),
new Banana(),
};

// ...And now we can add an apple to a list of bananas
bananas.ToList().Add(new Apple());
}


And of course we can just do this:

public void AlsoAllowed()
{
var fruit = new List<Fruit>();
fruit.Add(new Apple());
fruit.Add(new Banana());
}


The common argument for
List<T>
not being covariant (as per my understanding) is that doing so would allow us to add any arbitrary base object to a collection which contains derived objects. Maybe this is an oversimplification, but isn't that what the above examples are doing?

hvd hvd
Answer

When you do bananas.ToList().Add(new Apple()), bananas.ToList() creates a List<Fruit>. This is a list type which is able to contain any type of fruit. The fact that new Apple() can be added to that list makes sense.

bananas has type List<Banana>, which is a list type which is only able to contain bananas. There is no way to add new Apple() to this list, and your examples don't add new Apple() to this list. Your examples create a more tolerant list, and add to that, but leave the original list unmodified.