1. 基本概念
官方:協變和逆變都是術語,前者指能夠使用比原始指定的派生類型的派生程度更大(更具體的)的類型,后者指能夠使用比原始指定的派生類型的派生程度更小(不太具體的)的類型。[MSDN]
公式:
協變:IFoo<父類> = IFoo<子類>;
逆變:IBar<子類> = IBar<父類>;
暫時不理解沒關系,您接着往下看。
2. 協變(Covariance)
1) out關鍵字
對於泛型類型參數,out
關鍵字可指定類型參數是協變的。 可以在泛型接口和委托中使用 out
關鍵字。[MSDN]
2) 魯迅:一張圖勝過千言萬語(圖小看不清,單機鼠標右鍵 -> 在新標簽頁中打開圖片)
備注:泛型委托的協變原理也是一樣的。
3) 什么是協變?
協變就是對具體成員的輸出參數進行一次類型轉換,且類型轉換的准則是 “里氏替換原則”。
3. 逆變(Contravariance)
1) in關鍵字
對於泛型類型參數,in
關鍵字可指定類型參數是逆變的。 可以在泛型接口和委托中使用 in
關鍵字。[MSDN]
2) 魯迅:一張圖勝過千言萬語(圖小看不清,單機鼠標右鍵 -> 在新標簽頁中打開圖片)
備注:泛型委托的逆變原理也是一樣的。
3) 什么是逆變?
逆變就是對具體成員的輸入參數進行一次類型轉換,且類型轉換的准則是 “里氏替換原則”。
4. 自問自答
1)協變、逆變 為什么只能針對泛型接口或者委托?而不能針對泛型類?
因為它們都只能定義方法成員(接口不能定義字段),而方法成員在創建對象的時候是不涉及到對象內存分配的,所以它們是類型(內存)安全的。
為什么不針對泛型?因為泛型類是模板類,而類成員是包含字段的,不同類型的字段是影響對象內存分配的,沒有派生關系的類型它們是不兼容的,也是內存不安全的。
2)協變、逆變 為什么是類型安全的?
本質上是里氏替換原則,由里氏替換原則可知:派生程度小的是派生程度大的子集,所以子類替換父類的位置整個程序功能都不會發生改變。
3)官方對 協變、逆變 的定義現在是否能看懂?
上面看懂了,官方定義肯定也是沒問題的。派生程度小可以理解為基類,派生程度大可以理解為子類或派生類,至於為什么用程度這個詞,是因為繼承鏈的深度是沒限制的。
4)為什么 in 、out 只能是單向的(輸入或輸出)?
因為若類型參數同時為輸入參數和輸出參數,則必然會有一種轉換不符合里氏替換原則,即將父類型的變量賦值給子類型的變量,這是不安全的所以需要明確指定 in 或 out。
5. 協變和逆變的相互作用
首先我們先看一道經典的題目
1 interface IFoo<in T> { } 2 3 // 應該是 in 4 interface IBar<in T> 5 { 6 void Print(IFoo<T> foo); 7 } 8 9 // 還是應該是 out 10 interface IBar<out T> 11 { 12 void Print(IFoo<T> foo); 13 }
看過這道題目的人應該知道是 out ,但這是為什么呢?為什么 in 不行?要知道 in 行不行直接舉出一個反例即可,還是魯迅那句話 “一張圖勝過千言萬語”。
總結:
1. 將可變成員作為方法的輸入參數,則當前成員的泛型參數可變性必須與輸入成員的泛型參數可變性相反。
2. 將可變成員作為方法的返回參數,則當前成員的泛型參數可變性必須與輸出成員的泛型參數可變性相同。