2011-12-19 42 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)定義的原因