天天看點

c#4.0中的不變(invariant)、協變(covariant)、逆變(contravariant)小記

不變/協變/逆變,4.0中的這幾個概念越念越象繞密碼,如果單純死記硬背,就算記住了,時間長了還是會忘記的。

這裡隻是從應用的角度,簡單記錄一下:

從.net3.5開始,System命名空間裡就定義了一個泛型委托,原型如下:

即:輸入一個泛型參數T,傳回一個泛型結果TResult

假設有以下代碼:

在.net3.5環境下編譯會報錯:11行 fn2=fn1 這裡會提示

Cannot implicitly convert type 'System.Func<object,System.ArgumentException>' to 'System.Func<string,System.Exception>' 

即:無法隐式将System.Func<object,System.ArgumentException>轉換成System.Func<string,System.Exception>

說得更白一點,4.0以前的泛型委托,泛型參數一旦在執行個體使用過程中明确為具體類型後,是不能隐式自動轉換成其它類型的,哪怕類型是相容的(按道理來講,fn1中的輸入參數類型為object,由于string是繼承自object的,是以能用object的地方,string應該是能用的;同理:fn1中(傳回)輸出參數類型ArumentException繼承自Exception,是以傳回類型ArgumentException可以向上的轉化為Exception不會有任何問題,是以說fn1中的參數類型與fn2中的參數類型是安全相容的,但是編譯回不允許),這種不允許泛型參數類型變化的特點,稱為不變性(invariant).

而在4.0中,上面的代碼可正常編譯運作,如果研究下4.0中Func中的原型,會發現多了二個關鍵字:

即:在輸入參數T前加了一個in,而在輸出參數(也就是傳回參數)前加了一個out.

這樣編譯器就能自動将T隐式轉化為T的子類,而傳回類型TResult也能自動隐式轉化為它的父類。

說穿了就是OOP中的一個常理:子類與父類的繼承關系,其實就是is a的關系,是以任何能用父類做為輸入參數的地方,當然也能用子類作為替換(子承父業);而任何傳回子類的地方,當然也能安全的向上轉行為父類.(兒子是人類,父母當然也是人類,不可能是畜生,呵)

這時,我們稱T為逆變(ContraVariant)量,而TResult則為協變(CoVariant)量。記憶方法:向上轉型稱協變(因為這種轉型肯定是安全的,比較“和諧”),向下轉型稱逆變(因為不一定能轉型成功,有出錯的可能,稱逆變)

最後:in,out這二個關鍵字不僅能用于泛型委托,同樣也适用于泛型接口(比如4.0中的IEnumerable接口)