2010-02-05 59 views
16

一般差異在C#4.0已經以這樣一種方式,它可以編寫沒有例外以下(這是C#3.0中會發生什麼)來實現:一般差異在C#4.0

List<int> intList = new List<int>(); 
List<object> objectList = intList; 

[例非功能性:參見喬恩斯基特的答案]

我最近參加了一個會議,其中喬恩斯基特給通用差異的很好的概述,但我不知道我完全得到它 - 我理解的意義的inout關鍵詞當談到對立和協變時,但我很好奇幕後發生的事情。

當代碼執行時,CLR看到了什麼?它是否將List<int>隱含地轉換爲List<object>還是僅僅構建,我們現在可以在派生類型之間轉換爲父類型?

出於興趣,爲什麼在之前的版本中沒有介紹它,以及主要優點 - 即真實世界的用法?在這個post用於通用方差

更多信息(但問題是非常過時的,尋找真正的,向上的最新信息)

回答

20

沒有,你將不會是原因有三個工作:

  • 類(如List<T>)是不變的;只有代表和接口是變體
  • 爲了方差起作用,接口只能在一個方向上使用類型參數(in爲反變換,out爲協方差)
  • 值類型不支持作爲方差的類型參數 - 所以沒有從IEnumerable<int> converstion到IEnumerable<object>例如

(代碼失敗,在C#3.0和4.0編譯 - 沒有例外。)

所以這工作:

IEnumerable<string> strings = new List<string>(); 
IEnumerable<object> objects = strings; 

CLR只是使用引用,不變 - 沒有創建新對象。所以如果你打電話給objects.GetType(),你仍然會得到List<string>

我相信它之前沒有推出過,因爲語言設計者仍然必須弄清楚如何公開它的細節 - 從V2開始就一直在CLR中。

好處與您希望能夠使用另一種類型的其他時間相同。要使用我上星期六使用的相同示例,如果您有某種工具IComparer<Shape>按區域比較形狀,則很瘋狂,您無法使用該示例對List<Circle>進行排序 - 如果它可以比較任意兩個形狀,它當然可以比較任何兩個圈子。從C#4開始,將會有一個從IComparer<Shape>IComparer<Circle>的逆變換,因此您可以撥打circles.Sort(areaComparer)

+0

啊。我將不得不下載並觀看上週六的例子,並親自體驗一下。當然,這個概念本身是有道理的 - 只是試圖讓我的頭腦在真實世界中使用這個概念的思想。非常感謝回覆。 – 2010-02-05 15:13:40

+0

@丹尼爾:沒問題 - 對不起,我明確沒有在週六足夠好的解釋:)(當然有很多東西需要彌補......) – 2010-02-05 15:23:03

+0

哦,這根本就不是那個喬恩 - 它全部移動了一點快,我還沒有接觸到C#4的任何新功能 - 我像瘋子一樣亂寫筆記。看起來我必須訂購第二版C#的深度:) – 2010-02-05 15:25:22

8

出於興趣,爲什麼不這樣 在以前的版本

的第一個版本(1.x中).NET的沒有泛型可言,所以一般的方差遠介紹關閉。

應該指出的是,在.NET的所有版本中都有數組協方差。不幸的是,這是不安全的協方差:

Apple[] apples = new [] { apple1, apple2 }; 
Fruit[] fruit = apples; 
fruit[1] = new Orange(); // Oh snap! Runtime exception! Can't store an orange in an array of apples! 

在C#4中的合作和反方差是安全的,並防止這個問題。

什麼是主要好處 - 即真正的 世界用法?

代碼很多時候,你是調用API預計鹼(如IEnumerable<Base>)的放大類型,但是你所得到的是派生的(例如IEnumerable<Derived>)的放大型。

在C#2和C#3中,即使它應該「正常工作」,也需要手動轉換爲IEnumerable<Base>。合作和反方差使其「只是工作」。

p.s.完全吮吸Skeet的答案是吃掉我所有的代表點。該死的你,Skeet! :-)雖然他看起來像是answered this before

+1

需要說明的是:CLI和協變性總是支持的(其中「always」意爲「至少從v2開始」)。它只是在C#4.0之前沒有在C#中暴露*。但是,例如Eiffel.NET一直支持它,儘管AFAIK庫沒有正確註釋。 (不知道爲什麼,實際上,創建一個IL重寫器工具並不太容易,它只需要一個共同和反變化的接口列表,並在元數據中翻轉正確的位,即使你可以過去不用C#編寫,BCL就是用這種編寫的。) – 2010-02-05 19:23:02

14

一些額外的想法。

時執行該代碼

隨着喬恩和其他人正確地指出,我們不是在類,只有接口和委託方做什麼的CLR見。所以在你的例子中,CLR什麼也看不到;該代碼不能編譯。如果通過插入足夠的強制轉換來強制它進行編譯,那麼它會在運行時崩潰並導致錯誤的強制轉換異常。

現在,當問題發生時,如何在幕後進行變化仍然是一個合理的問題。答案是:我們限制這個參數化參數化接口和委託類型的類型參數的原因是,沒有任何發生在幕後。當你說

object x = "hello"; 

會發生什麼幕後是參考串扎進類型的對象的變量不加修改。構成對字符串的引用的位是合法位,以便作爲對象的引用,所以在這裏不需要發生任何事情。 CLR只是停止將這些位作爲引用字符串的思路,並開始將它們想象爲引用一個對象。

當你說:

IEnumerator<string> e1 = whatever; 
IEnumerator<object> e2 = e1; 

同樣的事情。什麼都沒發生。引用字符串枚舉器的位與引用對象枚舉器的位相同。有幾分當你做一個演員,其中進場更神奇,說:

現在CLR必須產生一個檢查,實際上E1確實實現了該接口,並檢查必須是聰明承認差異。

但是,我們可以通過不同的接口實現無操作轉換的原因是,因爲定期分配兼容性就是這種方式。你打算使用e2作什麼?

object z = e2.Current; 

返回對字符串的引用的位。我們已經確定,這些與對象兼容而不會改變。

爲什麼前面沒介紹過?我們還有其他功能要做,預算有限。

原理好處是什麼?從字符串序列到對象序列的轉換「正常工作」。注意到啊,