[英]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.AsReadOnly
的IReadOnlyList<IQuestion>
。 由於只讀列表不能添加奇怪的內容,因此可以正確地進行轉換。
問題是List<AMQuestion>
無法List<AMQuestion>
為IList<IQuestion>
,因此使用as
運算符無濟於事。 在這種情況下顯式轉換意味着將AMQuestion
為IQuestion
:
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.