簡體   English   中英

與C#Generics的協方差

[英]Covariance with C# Generics

給定一個接口IQuestion和接口的實現AMQuestion ,假設下面的例子:

List<AMQuestion> typed = new List<AMQuestion>();
IList<IQuestion> nonTyped = typed;

正如預期的那樣,這個例子產生一個編譯錯誤,說明兩者的類型不同。 但它表明存在明確的轉換。 所以我把它改成這樣:

List<AMQuestion> typed = new List<AMQuestion>();
IList<IQuestion> nonTyped = typed as IList<IQuestion>;

然后編譯,但在運行時, nonTyped始終為null。 如果有人可以解釋兩件事:

  • 為什么這不起作用。
  • 我怎樣才能達到預期的效果。

這將不勝感激。 謝謝!

AMQuestion實現IQuestion接口的事實不會轉換為從List<AMQuestion>派生的List<IQuestion>

因為此強制轉換是非法的,所以您的as運算符返回null

您必須單獨投射每個項目:

IList<IQuestion> nonTyped = typed.Cast<IQuestion>().ToList();

關於您的評論,請考慮以下代碼,以及常見的陳詞濫調動物示例:

//Lizard and Donkey inherit from Animal
List<Lizard> lizards = new List<Lizard> { new Lizard() };
List<Donkey> donkeys = new List<Donkey> { new Donkey() };

List<Animal> animals = lizards as List<Animal>; //let's pretend this doesn't return null
animals.Add(new Donkey()); //Reality unravels!

如果我們被允許將List<Lizard>轉換為List<Animal> ,那么理論上我們可以在該列表中添加一個新的Donkey ,這將破壞繼承。

為什么它不工作: as返回null ,如果該值的動態類型不能被澆鑄成目標類型,並List<AMQuestion>不能被強制轉換為IList<IQuestion>

但為什么不能呢? 好吧,檢查一下:

List<AMQuestion> typed = new List<AMQuestion>();
IList<IQuestion> nonTyped = typed as IList<IQuestion>;
nonTyped.Add(new OTQuestion());
AMQuestion whaaaat = typed[0];

IList<IQuestion>說“你可以向我添加任何IQuestion ”。 但如果它是List<AMQuestion>這是一個它無法保留的承諾。

現在,如果您不想添加任何內容,只需將其視為IQuestion兼容事物的集合,那么最好的做法是將其轉換為帶有List.AsReadOnlyIReadOnlyList<IQuestion> 由於只讀列表不能添加奇怪的內容,因此可以正確地進行轉換。

問題是List<AMQuestion>無法List<AMQuestion>IList<IQuestion> ,因此使用as運算符無濟於事。 在這種情況下顯式轉換意味着將AMQuestionIQuestion

IList<IQuestion> nonTyped = typed.Cast<IQuestion>.ToList();

順便說一句,你的標題中有“協方差”一詞。 IList ,類型不是協變的。 這正是演員陣容不存在的原因。 其原因是,該IList接口具有T在一些parameteres 並且在一些返回值,所以既沒有in也不out可用於T (@Sneftel有一個很好的例子來說明為什么不允許這種演員表。)

如果您只需要從列表中讀取,則可以使用IEnumerable

IEnumerable<IQuestion> = typed;

這將工作,因為IEnumerable<out T>擁有out定義,因為你不能傳遞一個T的參數。 您通常應該在代碼中使最弱的“承諾”保持可擴展性。

IList<T>對於T不是協變的; 它不能,因為接口定義了在“輸入”位置采用類型T值的函數。 但是, IEnumerable<T>對於T是協變的。 如果您可以將類型限制為IEnumerable<T> ,則可以執行以下操作:

List<AMQuestion> typed = new List<AMQuestion>();
IEnumerable<IQuestion> nonTyped = typed;

這不會對列表進行任何轉換。

你無法將List<AMQuestion>轉換為List<IQuestion> (假設AMQuestion實現了接口)的原因是必須對List<T>.Add等函數進行多次運行時檢查,以確保你真正添加AMQuestion

“as”運算符將始終返回null,因為不存在有效的強制轉換 - 這是已定義的行為。 您必須像這樣轉換或轉換列表:

IList<IQuestion> nonTyped = typed.Cast<IQuestion>().ToList();

具有泛型類型參數的類型只能是協變的,如果此泛型類型僅出現在讀訪問和逆變中,如果它僅出現在寫訪問中。 IList<T>允許對T類型的值進行讀寫訪問,因此它不能變體!

假設您被允許將List<AMQuestion>分配給IList<IQuestion>類型的變量。 現在讓我們實現一個class XYQuestion : IQuestion並在我們的IList<IQuestion>插入該類型的值,這似乎是完全合法的。 此列表仍引用List<AMQuestion> ,但我們無法將XYQuestion插入List<AMQuestion> 因此,兩種列表類型不兼容。

IList<IQuestion> list = new List<AMQuestion>(); // Not allowed!
list.Add(new XYQuestion()); // Uuups!

因為List<T>不是密封類,所以可能存在一個類型,它將繼承自List<AMQuestion>並實現IList<IQuestion> 除非你自己實現這樣一種類型,否則一個人實際存在的可能性極小。 盡管如此,例如說,這是完全合法的

class SillyList : List<AMQuestion>, IList<IQuestion> { ... }

並顯式實現IList<IQuestion>所有特定於類型的成員。 因此,如果“如果此變量包含對從List<AMQuestion>派生的類型的實例的引用,並且該實例的類型也實現IList<IQuestion> ,則將引用轉換為后一種類型也是完全合法的。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM