2011-12-19 40 views
3

所以,让我们说我有:协方差和逆变与名单VS IEnumerable的

Public Interface ISomeInterface 

End Interface 

Public Class SomeClass 
    Implements ISomeInterface 

End Class 

如果我有MyListList(Of SomeClass),我不能直接设置List(Of ISomeInterface) = MyList。但是,我可以设置一个IEnumerable(Of ISomeInterface) = MyList

随着我对协方差的理解,我认为它应该工作列表,因为List(Of T) implements IEnumerable(Of T)。显然,我错过了一些东西。

它为什么这样工作?具体为什么我不能做这样的事情:

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

其中动物实现IAnimal接口。但我可以做的:

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

http://blogs.msdn.com/b/ericlippert/archive/tags/covariance+and+contravariance/ – Oded 2011-12-19 18:37:44

+0

我已阅读了几篇文章,其中没有一篇帮助我回答这个特定的实例。你能指出特定的文章或其中的一部分,它涵盖了为什么我可以直接从特定类型的列表到界面列表? – Jay 2011-12-19 18:53:51

+0

http://blogs.msdn.com/b/ericlippert/archive/2007/10/26/covariance-and-contravariance-in-c-part-five-interface-variance.aspx – Oded 2011-12-19 19:10:58

回答

5

我记得看到了很多周围的信息在网络上这个问题之前,让我不知道我的答案是否真的会添加新的东西,但我会尝试。

如果您使用.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 
    } 
} 

您可以创建汽车的名单,只能包含对象从汽车衍生:

 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 

考虑上面两行代码的含义。这就是说车辆变量实际上是一个List<Car>对象,它应该只包含从Car派生的对象。但是,因为List<Vehicle>包含添加(车辆)方法,所以理论上可以将一个平面对象添加到List<Car>集合,这肯定是不正确的。

但是,将变量指定为汽车列表或飞机列表是完全有效的。

 IEnumerable<Vehicle> vehicles = cars; 

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

这里的快速解释是IEnumerable接口不允许您操作集合。它本质上是一个只读接口。 T对象(本例中为车辆)仅作为IEnumerable接口的Current属性的返回值公开。没有方法将Vehicle对象作为输入参数,因此不会以非法的方式修改集合。

边注意:我一直认为IList<T>接口是一个IReadableList<out T>接口和IWritableList<in T>接口的复合接口。

+0

感谢您的优秀细节。 – Jay 2011-12-19 19:54:04

+0

我同意分离阅读和书写界面是有意义的,但我会称之为'IReadableByIndex '和'IAppendable '。有可能让'List '继承'IAppendable '而不破坏兼容性(因为'List .Add(T)'的实现将被视为'IAppendable .Add(T)'的实现。目前还没有任何读写'List .this [int]'的实现可以被视为只读'IReadableByIndex 。这个[int]'的实现的任何方式。 – supercat 2012-10-19 15:23:48

+0

@supercat感谢您的评论!同意你...起初,当我想到这个想法时,我一直在努力 - 起初我正在考虑沿着“IReadOnlyList”的方向,但正如你所提到的,将列表称为只读列表是不正确的。那时我改变了我的想法,只是调用接口IReadableList。它没有声称底层类型是只读集合。它只是定义了界面的可读部分,以便您可以为消费者提供更有针对性的界面。 – 2012-10-23 14:24:17

2

这可能有助于想想你可以用你的List(Of SomeClass)做什么你就分配到List(Of ISomeInterface)变量之后。

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

这是一个协方差没有在这种情况下List(Of T)定义的原因