Search code examples
c#genericscovarianceparametric-polymorphism

Why does mutability preclude covariance?


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>?


Solution

  • 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.