2010-01-09 44 views
44

請看下面的例子(從MSDN Blog部分截取):C#方差問題:分配列表<Derived>方式List <Base>

class Animal { } 
class Giraffe : Animal { } 

static void Main(string[] args) 
{ 
    // Array assignment works, but... 
    Animal[] animals = new Giraffe[10]; 

    // implicit... 
    List<Animal> animalsList = new List<Giraffe>(); 

    // ...and explicit casting fails 
    List<Animal> animalsList2 = (List<Animal>) new List<Giraffe>(); 
} 

這是一個協方差問題?這將在未來的C#版本中得到支持,並且是否有任何巧妙的解決方法(僅使用.NET 2.0)?

回答

95

嗯,這肯定不會是在C#4支持有一個根本性的問題:

List<Giraffe> giraffes = new List<Giraffe>(); 
giraffes.Add(new Giraffe()); 
List<Animal> animals = giraffes; 
animals.Add(new Lion()); // Aargh! 

保持長頸鹿安全:只說不不安全的差異。

陣列版本工作,因爲數組支持引用類型方差,執行時間檢查。泛型的要點是提供編譯時間類型安全。

在C#4中將會支持安全通用差異,但僅限於接口和委託。所以,你可以做:

Func<string> stringFactory =() => "always return this string"; 
Func<object> objectFactory = stringFactory; // Safe, allowed in C# 4 

Func<out T>T因爲T僅在輸出位置使用。相比之下,與Action<in T>T因爲T在輸入位置僅用於有哪些是逆變的,使這個安全:

Action<object> objectAction = x => Console.WriteLine(x.GetHashCode()); 
Action<string> stringAction = objectAction; // Safe, allowed in C# 4 

IEnumerable<out T>是協變的爲好,使這個正確的C#4,如被他人指出:

IEnumerable<Animal> animals = new List<Giraffe>(); 
// Can't add a Lion to animals, as `IEnumerable<out T>` is a read-only interface. 

在C#2解決此工作在你的情況而言,你需要保持一個列表,或者你會很樂意建立新的名單?如果這是可以接受的,List<T>.ConvertAll是你的朋友。

+0

+1只是爲了增加Jon的回答(不是他需要任何幫助),遵循'Func '到'Func '的例子和'T'在'Action'中是逆變的事實。以下'行動 =動作'不會在C#4中工作。 – 2010-01-09 16:17:01

+0

感謝您也指出類型安全問題。這真的很有幫助。 – AndiDog 2010-01-09 16:25:05

+0

'animals.Add(new Lion()); //唉!'?如果您可以投出一個「獅子」並將其用作「動物」,並且如果您將「動物」中的所有元素用作「動物」,那麼問題是什麼? – Jeff 2017-04-12 20:04:17

11

它將工作在C#4 IEnumerable<T>,所以你可以做:

IEnumerable<Animal> animals = new List<Giraffe>(); 

然而List<T>不是covarient投影,所以你在上面所做的,因爲你可以做到這一點,你不能分配列表:

List<Animal> animals = new List<Giraffe>(); 
animals.Add(new Monkey()); 

這顯然是無效的。

6

根據List<T>,恐怕你運氣不好。但是,.NET 4.0/C#4.0增加了對協變/反變接口的支持。具體而言,現在將IEnumerable<T>定義爲IEnumerable<out T>,這意味着類型參數現在是協變

這意味着你可以做這樣的事情在C#4.0 ...

// implicit casting 
IEnumerable<Animal> animalsList = new List<Giraffe>(); 

// explicit casting 
IEnumerable<Animal> animalsList2 = (IEnumerable<Animal>) new List<Giraffe>(); 

注:數組類型也已經(至少從.NET 1.1)協變的。

我認爲對於IList<T>和其他類似的通用接口(甚至是泛型類)沒有增加方差支持是很遺憾的,但是至少我們有一些東西。

+7

你不能用聲明方差異註釋安全地使IList 協變。 – 2010-01-09 19:34:36

4

其他人已經提到,可變集合不支持協變性/逆變性,因爲在編譯時無法保證類型安全;然而,這是可以做到在C#3.5的快速單向轉換,如果這是你在找什麼:

List<Giraffe> giraffes = new List<Giraffe>(); 
List<Animal> animals = giraffes.Cast<Animal>().ToList(); 

當然它是不一樣的東西,這不是真正的協方差 - 你實際上是創建另一個列表,但這可以說是一種「解決方法」。

在.NET 2.0中,你可以利用陣列協方差來簡化代碼:

List<Giraffe> giraffes = new List<Giraffe>(); 
List<Animal> animals = new List<Animal>(giraffes.ToArray()); 

但要知道,你實際上創建新的集合在這裏。

+0

我認爲這些對我的簡單應用來說是非常好的解決方法。至少它們對可讀性好 - 不是爲了性能。 – AndiDog 2010-01-09 16:26:06

+0

@JohnAskew:我不確定你的意思在這裏 - 'IList '上沒有'Cast'方法,它是'Enumerable .Cast '擴展方法,它需要'IEnumerable '元素轉換爲'T2',然後返回它(作爲IEnumerable 而不是IList )。它與IList '完全無關,除了'IList '正好從'IEnumerable '繼承並因此支持'Enumerable '擴展方法。那裏沒有任何協變。 – Aaronaught 2014-07-22 14:17:04

+0

糟糕...我單擊刪除而不是編輯:-S – 2014-07-23 14:14:32

相關問題