Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Covariance & Contravariance with List vs IEnumerable

So, let's say I have:

Public Interface ISomeInterface

End Interface

Public Class SomeClass
  Implements ISomeInterface

End Class

If I have MyList as List(Of SomeClass), I cannot directly set a List(Of ISomeInterface) = MyList. However, I can Set an IEnumerable(Of ISomeInterface) = MyList.

With my understanding of Covariance I thought that it should work list to list since List(Of T) implements IEnumerable(Of T). Clearly, I am missing something.

Why does it work that way? Specifically why Can't I do something like:

Dim Animals As new List(Of Animal)
Dim Cats As List(Of IAnimal) = Animals

Where Animal implements the IAnimal Interface. But I can do:

Dim Animals As New List(Of Animal)
Dim Cats As IEnumerable(Of IAnimal) = Animals
like image 815
Jay Avatar asked Dec 06 '25 03:12

Jay


2 Answers

I recall seeing a lot of information around this issue on the web previously, so I'm not sure that my answer will really add anything new, but I'll try.

If you're using .NET 4, then notice that the definition of IEnumerable(Of T) is actually IEnumerable(Of Out T). The new Out keyword has been introduced in version 4, which indicates the covariance of this interface. The List(Of T) class, however, is simply defined as List(Of T). The Out keyword is not used here, so that class is not covariant.

I'll provide some examples to try to explain why certain assignments such as the one you're describing can't be done. I see that your question is written in VB, so my apologies for using C#.

Assume that you have the following classes:

abstract class Vehicle
{
    public abstract void Travel();
}

class Car : Vehicle
{
    public override void Travel()
    {
        // specific implementation for Car
    }
}

class Plane : Vehicle
{
    public override void Travel()
    {
        // specific implementation for Plane
    }
}

You can create a list of cars, which can only contain objects derived from Car:

        List<Car> cars = new List<Car>();

You can also create a list of planes, which can only contain objects derived from Plane:

        List<Plane> planes = new List<Plane>();

You can even create a list of Vehicles, which can contain any object derived from Vehicle:

        List<Vehicle> vehicles = new List<Vehicle>();

It's legal to add a car to the list of cars, and it's legal to add a plane to the list of planes. It's also legal to add both a car and a plane to the list of vehicles. Therefore, all of the following lines of code are valid:

        cars.Add(new Car()); // add a car to the list of cars

        planes.Add(new Plane()); // add a plane to the list of planes

        vehicles.Add(new Plane()); // add a plane to the list of vehicles
        vehicles.Add(new Car()); // add a car to the list of vehicles

It's not legal to add a car to the list of planes, nor is it legal to add a plane to the list of cars. The following lines of code won't compile:

        cars.Add(new Plane()); // can't add a plane to the list of cars
        planes.Add(new Car()); // can't add a car to the list of planes

Therefore, it's not legal to try to bypass this restriction by assigning the list of cars or the list of planes to the vehicles variable:

        vehicles = cars; // This is not allowed
        vehicles.Add(new Plane()); // because then you could do this

Consider what the two lines of code are saying above. It's saying that the vehicles variable is actually a List<Car> object, which should only contain objects derived from Car. However, because List<Vehicle> contains an Add(Vehicle) method, it would theoretically be possible to add a Plane object to the List<Car> collection, which is definitely not correct.

However, it's perfectly valid to assign a list of cars or a list of planes to an IEnumerable<Vehicle> variable.

        IEnumerable<Vehicle> vehicles = cars;

        foreach (Vehicle vehicle in vehicles)
        {
            vehicle.Travel();
        }

The quick explanation here is that the IEnumerable interface doesn't allow you to manipulate the collection. It is essentially a read-only interface. The T objects (Vehicles in this case) are only exposed as a return value on the IEnumerable interface's Current property. There are no methods that take Vehicle objects as input parameters, therefore there is no danger of the collection being modified in an illegal way.

Side Note: I've always thought that it would make sense for the IList<T> interface to be a composite of an IReadableList<out T> interface and an IWritableList<in T> interface.

like image 191
Dr. Wily's Apprentice Avatar answered Dec 11 '25 05:12

Dr. Wily's Apprentice


It might help to think about what you could do with your List(Of SomeClass) after you've assigned it to a List(Of ISomeInterface) variable.

You could add any object which implements ISomeInterface to it, e.g. SomeOtherClass, and no longer have a valid List(Of SomeClass)

This is the reason that covariance is not defined for List(Of T) in this case

like image 38
Patrick McDonald Avatar answered Dec 11 '25 04:12

Patrick McDonald



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!