c#中泛型的協變與逆變:詳解
in(泛型修飾符)(C# 參考)
Visual Studio 2013 其他版本對於泛型型別引數,in 關鍵字指定該型別引數是逆變的。 可以在泛型介面和委託中使用 in 關鍵字。
通過逆變,可以使用與泛型引數指定的派生型別相比,派生程度更小的型別。 這樣可以對委託型別和實現變體介面的類進行隱式轉換。 引用型別支援泛型型別引數中的協變和逆變,但值型別不支援。
在泛型介面或委託中,如果型別形參僅用作方法返回型別,而不用於方法實參,則可宣告為協變的。 Ref 和 out 引數不能為變體。
如果介面具有逆變型別形參,則允許其方法接受與介面型別形參指定的派生型別相比,派生程度更小的型別的實參。
可以向逆變委託分配同一型別的其他委託,但需使用派生程度較小的泛型型別引數。
示例下例演示如何宣告、擴充套件和實現一個逆變泛型介面。 此外還演示瞭如何對實現此介面的類使用隱式轉換。
C#// Contravariant interface. interface IContravariant<inA> { } // Extending contravariant interface. interface IExtContravariant<in A> : IContravariant<A> { } // Implementing contravariant interface. class Sample<A> : IContravariant<A> { } class Program { static void Test() { IContravariant<Object> iobj = newSample<Object>(); IContravariant<String> istr = new Sample<String>(); // You can assign iobj to istr because // the IContravariant interface is contravariant. istr = iobj; } }
下例演示如何宣告、例項化和呼叫一個逆變泛型委託。 此外還演示瞭如何隱式轉換委託型別。
C#// Contravariant delegate. public delegate void DContravariant<in A>(A argument); // Methods that match the delegate signature. public static void SampleControl(Control control) { } public static void SampleButton(Button button) { } public void Test() { // Instantiating the delegates with the methods. DContravariant<Control> dControl = SampleControl; DContravariant<Button> dButton = SampleButton; // You can assign dControl to dButton // because the DContravariant delegate is contravariant. dButton = dControl; // Invoke the delegate. dButton(new Button()); }具體要看維基百科的解釋:http://zh.wikipedia.org/wiki/%E5%8D%8F%E5%8F%98%E4%B8%8E%E9%80%86%E5%8F%98
還是維基給力,摘錄其中一段:
首先考慮陣列型別構造器: 從Animal型別,可以得到Animal[](“animal陣列”)。 是否可以把它當作
- 協變:一個Cat[]也是一個Animal[]
- 逆變:一個Animal[]也是一個Cat[]
- 以上二者均不是(不變)?
如果要避免型別錯誤,且陣列支援對其元素的讀、寫操作,那麼只有第3個選擇是安全的。Animal[]並不是總能當作Cat[],因為當一個客戶讀取陣列並期望得到一個Cat,但Animal[]中包含的可能是個Dog。所以逆變規則是不安全的。
反之,一個Cat[]也不能被當作一個Animal[]。因為總是可以把一個Dog放到Animal[]中。在協變陣列,這就不能保證是安全的,因為背後的儲存可以實際是Cat[]。因此協變規則也不是安全的—陣列構造器應該是不變。注意,這僅是可寫(mutable)陣列的問題;對於不可寫(只讀)陣列,協變規則是安全的。
這示例了一般現像。只讀資料型別(源)是協變的;只寫資料型別(匯/sink)是逆變的。可讀可寫型別應是“不變”的。
更深入瞭解,請看此文章,個人看了之後豁然開朗。
先不考慮型別,我們考慮數學意義上的整數。考慮整數之間的小於關係——≤。這裡關係實際上就是一個方法,接受2個數,返回布林值。
現在我們考慮一下對於整數的投影,投影指的是一個函式,接受一個整數,返回另一個整數。比如 z → z + z 我們可以定義為D(double), z → 0 - z定義為N for (negate), z → z * z,定義為S(square).
問題就出來了,是不是所有的情況下, (x ≤ y) = (D(x) ≤ D(y))?事實上就是,如果x小於等於y,那麼x的2倍也小於等於y的2倍。投影D保留了不等號的方向。
對於N,很顯然,1 ≤ 2 但是 -1 ≥ -2。即(x ≤ y) = (N(y) ≤ N(x)),投影N反轉了不等號的方向。
對於S, -1 ≤ 0, S(0) ≤ S(-1),但是 1 ≤ 2, S(2)≥ S(1)。可見投影S即沒保留不等號方向,也沒反轉不等號方向。
投影D就是協變的,它保留了整數上的次序關係。投影N是逆變的,它反轉了整數上的次序關係,投影S兩者都不是,所以是不變的。
所以這裡很清楚,整數自身不是變體,小於關係也不是變體。投影才是協變或者逆變——接受一個整數生成一個新整數。