Ковариантность и контравариантность со списком против IEnumerable - PullRequest
3 голосов
/ 19 декабря 2011

Итак, допустим, у меня есть:

Public Interface ISomeInterface

End Interface

Public Class SomeClass
  Implements ISomeInterface

End Class

Если у меня MyList как List(Of SomeClass), я не могу напрямую установить List(Of ISomeInterface) = MyList.Тем не менее, я могу установить IEnumerable(Of ISomeInterface) = MyList.

С моим пониманием Covariance я подумал, что это должен работать список к списку, поскольку List(Of T) реализует IEnumerable(Of T).Очевидно, я что-то упустил.

Почему это так работает?В частности, почему я не могу сделать что-то вроде:

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

Где Animal реализует интерфейс IAnimal.Но я могу сделать:

Dim Animals As New List(Of Animal)
Dim Cats As IEnumerable(Of IAnimal) = Animals

Ответы [ 2 ]

5 голосов
/ 19 декабря 2011

Я помню, что видел много информации об этой проблеме в Интернете ранее, поэтому я не уверен, что мой ответ действительно добавит что-то новое, но я попробую.

Если вы используете .NET 4, то обратите внимание, что определение IEnumerable (Of T) на самом деле IEnumerable (Of Out T). Новое ключевое слово Out было введено в версии 4, что указывает на ковариацию этого интерфейса. Класс List (Of T), однако, просто определяется как List (Of T). Ключевое слово Out здесь не используется, поэтому класс не является ковариантным.

Я приведу несколько примеров, чтобы попытаться объяснить, почему некоторые задания, такие как описываемые вами, не могут быть выполнены. Я вижу, что ваш вопрос написан на VB, поэтому мои извинения за использование C #.

Предположим, что у вас есть следующие классы:

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

Вы можете создать список автомобилей, который может содержать только объекты, полученные из Car:

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

Вы также можете создать список плоскостей, который может содержать только объекты, полученные из плоскости:

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

Вы даже можете создать список Транспортных средств, который может содержать любой объект, полученный из Транспортного средства:

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

Законно добавлять машины в список машин, и разрешено добавлять самолеты в список самолетов. Также законно добавить в список транспортных средств как автомобиль, так и самолет. Таким образом, все следующие строки кода являются действительными:

        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

Не разрешается добавлять автомобиль в список самолетов, а также не разрешается добавлять самолет в список автомобилей. Следующие строки кода не будут компилироваться:

        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

Поэтому нельзя пытаться обойти это ограничение, присваивая переменной список автомобилей или список самолетов:

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

Посмотрите, что две строки кода говорят выше. Это говорит о том, что переменная Vehicles на самом деле является List<Car> объектом, который должен содержать только объекты, полученные из Car. Однако, поскольку List<Vehicle> содержит метод Add (Vehicle), теоретически было бы возможно добавить объект Plane в коллекцию List<Car>, что, безусловно, не правильно.

Однако вполне допустимо назначить список автомобилей или список самолетов переменной IEnumerable<Vehicle>.

        IEnumerable<Vehicle> vehicles = cars;

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

Краткое объяснение здесь состоит в том, что интерфейс IEnumerable не позволяет вам управлять коллекцией. По сути, это интерфейс только для чтения. Объекты T (Транспортные средства в данном случае) отображаются только как возвращаемое значение в свойстве Current интерфейса IEnumerable. Нет методов, принимающих объекты Vehicle в качестве входных параметров, поэтому нет опасности, что коллекция будет изменена незаконным образом.

Примечание: я всегда думал, что было бы целесообразно, чтобы интерфейс IList<T> был составным из интерфейса IReadableList<out T> и интерфейса IWritableList<in T>.

2 голосов
/ 19 декабря 2011

Может быть полезно подумать о том, что вы могли бы сделать с вашим List(Of SomeClass) после того, как вы присвоили его переменной List(Of ISomeInterface).

Вы можете добавить любой объект, который реализует ISomeInterface,например, SomeOtherClass, и больше не имеют действительного List(Of SomeClass)

Это причина того, что ковариация не определена для List(Of T) в этом случае

...