最近在看《C#高級編程(第九版)》這本書,看到了泛型接口這章。其中關於協變和逆變沒太理解,講得有點坑爹,網上查了許多資料,總算(感受)弄清楚了,來這裏記錄一下。編程
先從字面上理解 協變(Covariance)、逆變(Contravariance)。函數
co- 是英文中表示「協同」、「合做」的前綴。協變 的字面意思就是 「與變化的方向相同」。
contra- 是英文中表示「相反」的前綴,逆變 的字面意思就是是 「與變化方向相反」。code
那麼問題來了,挖掘機技術哪家強?這裏的 變化方向 指的是什麼?對象
C# 中對於對象(即對象引用),僅存在一種隱式類型轉換,即 子類型的對象引用到父類型的對象引用的轉換。這裏的變化指的就是這種 子->父 的類型轉換。接口
object o = "hello"; //string (子類)類型的引用轉換爲 object (父類)類型的引用
協變與逆變雖然從名字上看是兩個徹底相反的轉換,但其實只是「子類型引用到父類型引用」這一過程在函數中使用的 兩個不一樣階段 而已,接下來將詳細說明這點。string
假設有一函數,接收 object
類型的參數,輸出 string
類型的返回值:class
string Method(object o) { return "abc"; }
那麼在Main
函數中咱們能夠這樣調用它:泛型
string s = "abc"; object o = Method(s);
注意,這裏發生了兩次隱式類型轉換:object
在向函數輸入時,參數 s
由 string
類型轉換爲 object
類型引用
在函數輸出(返回)時,返回值 由 string
類型轉換爲 object
類型
咱們這裏能夠看做是函數簽名可發生變換(不論函數的內容,不影響結果):
string Method(object o)
可變換爲 string Method(string o)
string Method(string o)
可變換爲 object Method(string o)
也就是說,在函數輸入時,函數的 輸入類型 可由 object
變換爲 string
,父->子
在函數輸出時,函數的 輸出類型 可由string
變換爲object
,子->父
in
、out
參數in
、out
的狀況假設有一泛型接口,而且有一個類實現了此接口:
interface IDemo<T> { T Method(T value); } public class Demo : IDemo<string> { //實現接口 IDemo<string> public string Method(string value) { return value; } }
在Main
函數中這樣寫:
IDemo<string> demoStr = new Demo(); IDemo<object> demoObj = demoStr;
上面的這段代碼中的第二行包含了一個假設:
IDemo<string>
類型可以隱式轉換爲 IDemo<object>
類型
這乍看上去就像「子類型引用轉換爲父類型引用」 同樣,然而很遺憾,他們並不相同。假如能夠進行隱式類型轉換,那就意味着:
string Method(string value)
能轉換爲 object Method(object value)
從上一節中咱們知道,在函數這輸入和輸出階段,其類型可變化方向是不一樣的。
因此在C#中,要想應用泛型接口類型的隱式轉換,須要討論「輸入」和「輸出」兩種狀況。
interface IDemo<out T> { //僅將類型 T 用於輸出 T Method(object value); } public class Demo : IDemo<string> { //實現接口 public string Method (object value) { //別忘了類型轉換! return value.ToString(); } }
在Main
函數中這樣寫:
IDemo<string> demoStr = new Demo(); IDemo<object> demoObj = demoStr;
可將 string Method (object value)
轉換爲 object Method (object value)
便可將 IDemo<string>
類型轉換爲 IDemo<object>
類型。
僅從泛型的類型上看,這是 「子->父」 的轉換,與第一節中提到的轉換方向相同,稱之爲「協變」。
同理咱們能夠給 T
加上 in
參數:
interface IDemo<in T> { //僅將類型 T 用於輸入 string Method(T value); } public class Demo : IDemo<object> { //實現接口 public string Method (object value) { return value.ToString(); } }
在Main
函數中這樣寫:
IDemo<object> demoObj = new Demo(); IDemo<string> demoStr = demoObj;
這裏可將 string Method (object value)
轉換爲 string Method (string value)
便可將 IDemo<object>
類型轉換爲 IDemo<string>
類型。
僅從泛型的類型上看,這是 「父->子」 的轉換,與第一節中提到的轉換方向相反,稱之爲「逆變」,有時也譯做「抗變」或「反變」。
以上只討論了協變與逆變在方法中的狀況,其實在屬性中狀況也相相似,再也不說明。
可能你們也發現了,所謂「協」與「逆」都是隻是一種表象,其內在本質爲同一過程。
「協變」與「逆變」中的「協」與「逆」表示泛型接口在將類型參數僅用於輸入或輸出的狀況下,其類型參數的隱式轉換所遵循的規律。
當泛型接口類型僅用於輸出(使用關鍵詞 out),其類型參數隱式轉換所遵循的規律與對象引用的類型轉換規律相同,稱之爲「協變」
當泛型接口類型僅用於輸入(使用關鍵詞 in),其類型參數隱式轉換所遵循的規律與對象引用的類型轉換規律相反,稱之爲「逆變」、「抗變」或「反變」。
=。= 感受強類型語言有點坑爹啊