Search code examples
c#genericscovariance

Why doesn't C# cast Container<Derived> to Container<Base> implicitly?


Consider the following code snippet:

class Animal {}
class Dog : Animal {}

interface IMyContainer<T> {
    T Get(int i);
    void Add (T thing);
}

class MyList<T> : IMyContainer<T> {
    private List<T> items = new List<T>();
    public T Get(int i) { return items[i]; }
    public void Add(T item) { items.Add(item); }
}


public static void Main(string[] args)
{
    IMyContainer<Dog> dogs = new MyList<Dog>();

    // Why doesn't this cast dogs to animals?
    IMyContainer<Animal> animals = dogs;
    animals.Add(new Animal());
    Animal retrievedAnimal = animals.Get(0);
    animals.Add(new Dog());
}

I know the above C# will fail because generics are invariant. I know the line IMyContainer<Animal> animals = dogs; will fail because, even in situations where you may refer to a MyList<Dog> as a IMyContainer<Animal> (where the container is covariant), it is still a MyList<Dog> and the misleading reference might get users to add a Cat to a MyList<Dog>, which is why in such a case the interface cannot contain methods that accept T.

My question is: Why doesn't the compiler simply cast the dogs object to a IMyContainer<Animal> when the reference is changed? It makes logical sense to me, and from that point forward the reference appropriately points to an object of the correct type and we are free to add and remove any Animals from our container. It seems all the type issues arise when we use a reference to refer to an object that is a subset of the reference type but not equal to it. Is such a cast impossible for the compiler to infer implicitly?


Solution

  • If I'm reading your question correctly, you seem to be under a misconception as to what a cast actually is. You seem to think that casting actually changes the type of an object. It does not. It simply provides access to the same object via a reference of a different type. The word "cast" is used in the same sense as "to cast something in a different light", i.e. to look at the same thing in a different way. You say:

    from that point forward the reference appropriately points to an object of the correct type and we are free to add and remove any Animals from our container

    That would require the actual object to change from a MyContainer<Dog> to a MyContainer<Animal>, because you obviously can't add anything but a Dog to a MyContainer<Dog>. What you're talking about is a conversion, not a cast, and that is definitely not going to happen implicitly. How could it, given that the same object could still be referred to by another variable of the original type? That would require this:

    var dogs = new MyContainer<Dog>();
    MyContainer<Animal> animals = dogs;
    

    to result in dogs referring to a MyContainer<Animal>, which obviously doesn't make sense.