I'm trying to understand how covariance works (in general, though my examples will be in C#) and what governs when a generic type can be covariant. Assume we have a class Animal
with subclasses Cat
and Dog
. I get why a read-only type such as IEnumerable<T>
is covariant, allowing this code to compile:
IEnumerable<Animal> initialAnimalEnumerable = new List<Cat> {new Cat() };
IEnumerable<Animal> addedAnimalEnumerable = initialAnimalEnumerable.Append(new Dog());
because the elements of the enumerable can only be treated as type Animal
; the information that they're Cats/Dogs specifically is lost. What I don't understand is why this can't apply with mutable collections such as List<T>
:
List<Animal> initialAnimalList = new List<Cat> {new Cat() }; // error CS0266: Cannot implicitly convert type 'List<Cat>' to 'List<Animal>'
initialAnimalList[0] = new Dog();
Explanations on questions like C# variance problem seem to assume that this would cause a problem, because we're trying to add a Dog
to a List<Cat>
...but why is it still treated as a List<Cat>
instead of a List<Animal>
?
You're assuming a simple case where the only reference to the List<Cat>
was the one used in assigning initialAnimalList
. What if instead of creating a new object/reference you were instead assigning from another existing variable:
E.g.:
var cats = new List<Cat> {new Cat() };
List<Animal> initialAnimalList = cats; // error CS0266
initialAnimalList[0] = new Dog();
We still have cats
. It's type hasn't magically changed to List<Animal>
due to some later assignment, and any later code that accesses cats
is entitled to assume it won't find a Dog
in there.
The assignment doesn't change the type of the object.