协方差&与List vs IEnumerable的逆转

时间:2011-12-19 18:30:58

标签: .net covariance

所以,让我说我有:

Public Interface ISomeInterface

End Interface

Public Class SomeClass
  Implements ISomeInterface

End Class

如果MyListList(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 个答案:

答案 0 :(得分:5)

我记得以前在网上看过很多关于这个问题的信息,所以我不确定我的回答是否真的会添加任何新内容,但我会尝试。

如果你正在使用.NET 4,那么请注意IEnumerable(Of T)的定义实际上是IEnumerable(Of Out T)。版本4中引入了新的Out关键字,表示此接口的协方差。然而,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
    }
}

您可以创建汽车列表,其中只能包含从汽车派生的对象:

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

您还可以创建一个平面列表,该列表只能包含从平面派生的对象:

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

您甚至可以创建一个车辆列表,其中可以包含从Vehicle派生的任何对象:

        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

考虑上面两行代码的含义。它说车辆变量实际上是一个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对象(在这种情况下为Vehicle)仅作为IEnumerable接口的Current属性的返回值公开。没有将Vehicle对象作为输入参数的方法,因此不存在以非法方式修改集合的危险。

备注:我一直认为IList<T>接口是IReadableList<out T>接口和IWritableList<in T>接口的组合是有意义的。

答案 1 :(得分:2)

在将List(Of SomeClass)变量分配给List(Of ISomeInterface)后,考虑一下ISomeInterface可以做些什么。

您可以添加任何实现SomeOtherClass的对象,例如List(Of SomeClass),并且不再拥有有效的List(Of T)

这就是在这种情况下没有为{{1}}定义协方差

的原因