最近在看《C#高級(jí)編程(第九版)》這本書隙笆,看到了泛型接口這章。其中關(guān)于協(xié)變和逆變沒太理解,講得有點(diǎn)坑爹撑柔,網(wǎng)上查了許多資料煤率,總算(感覺)弄清楚了,來這里記錄一下乏冀。
一蝶糯、協(xié)變和逆變是什么?
先從字面上理解 協(xié)變(Covariance)辆沦、逆變(Contravariance)昼捍。
co- 是英文中表示“協(xié)同”、“合作”的前綴肢扯。協(xié)變 的字面意思就是 “與變化的方向相同”妒茬。
contra- 是英文中表示“相反”的前綴,逆變 的字面意思就是是 “與變化方向相反”蔚晨。
那么問題來了乍钻,這里的 變化方向 指的是什么?
C# 中對(duì)于對(duì)象(即對(duì)象引用)铭腕,僅存在一種隱式類型轉(zhuǎn)換银择,即 子類型的對(duì)象引用到父類型的對(duì)象引用的轉(zhuǎn)換。這里的變化指的就是這種 子->父 的類型轉(zhuǎn)換累舷。
object o = "hello";
//string (子類)類型的引用轉(zhuǎn)換為 object (父類)類型的引用
協(xié)變與逆變雖然從名字上看是兩個(gè)完全相反的轉(zhuǎn)換浩考,但其實(shí)只是“子類型引用到父類型引用”這一過程在函數(shù)中使用的 兩個(gè)不同階段 而已,接下來將詳細(xì)說明這點(diǎn)被盈。
二析孽、使用函數(shù)的不同階段發(fā)生的類型轉(zhuǎn)換
假設(shè)有一函數(shù),接收 object 類型的參數(shù)只怎,輸出 string 類型的返回值:
string Method(object o)
{
return "abc";
}
那么在Main函數(shù)中我們可以這樣調(diào)用它:
string s = "abc";
object o = Method(s);
注意袜瞬,這里發(fā)生了兩次隱式類型轉(zhuǎn)換:
- 在向函數(shù)輸入時(shí),參數(shù) s 由 string 類型轉(zhuǎn)換為 object 類型
- 在函數(shù)輸出(返回)時(shí)身堡,返回值 由 string 類型轉(zhuǎn)換為 object 類型
我們這里可以看作是函數(shù)簽名可發(fā)生變換(不論函數(shù)的內(nèi)容邓尤,不影響結(jié)果): - string Method(object o) 可變換為 string Method(string o)
- string Method(string o) 可變換為 object Method(string o)
也就是說,在函數(shù)輸入時(shí)盾沫,函數(shù)的 輸入類型 可由 object 變換為 string裁赠,父->子
在函數(shù)輸出時(shí),函數(shù)的 輸出類型 可由string變換為object赴精,子->父
三佩捞、理解泛型接口中的 in、out參數(shù)
沒有指定in蕾哟、out的情況
假設(shè)有一泛型接口一忱,并且有一個(gè)類實(shí)現(xiàn)了此接口:
interface IDemo<T>
{
T Method(T value);
}
public class Demo : IDemo<string>
{
//實(shí)現(xiàn)接口 IDemo<string>
public string Method(string value)
{
return value;
}
}
在Main函數(shù)中這樣寫:
IDemo<string> demoStr = new Demo();
IDemo<object> demoObj = demoStr;
上面的這段代碼中的第二行包含了一個(gè)假設(shè):
IDemo<string> 類型能夠隱式轉(zhuǎn)換為 IDemo<object> 類型
這乍看上去就像“子類型引用轉(zhuǎn)換為父類型引用” 一樣莲蜘,然而很遺憾,他們并不相同帘营。假如可以進(jìn)行隱式類型轉(zhuǎn)換票渠,那就意味著:
string Method(string value) 能轉(zhuǎn)換為 object Method(object value)
從上一節(jié)中我們知道,在函數(shù)這輸入和輸出階段芬迄,其類型可變化方向是不同的问顷。所以在C#中,要想應(yīng)用泛型接口類型的隱式轉(zhuǎn)換禀梳,需要討論“輸入”和“輸出”兩種情況杜窄。
接口僅用于輸出的情況,協(xié)變
interface IDemo<out T>
{
//僅將類型 T 用于輸出
T Method(object value);
}
public class Demo : IDemo<string>
{
//實(shí)現(xiàn)接口
public string Method (object value)
{
//別忘了類型轉(zhuǎn)換算途!
return value.ToString();
}
}
在Main函數(shù)中這樣寫:
IDemo<string> demoStr = new Demo();
IDemo<object> demoObj = demoStr;
可將 string Method (object value) 轉(zhuǎn)換為 object Method (object value)
即可將 IDemo<string> 類型轉(zhuǎn)換為 IDemo<object> 類型塞耕。
僅從泛型的類型上看,這是 “子->父” 的轉(zhuǎn)換嘴瓤,與第一節(jié)中提到的轉(zhuǎn)換方向相同扫外,稱之為“協(xié)變”。
接口僅用于輸入的情況廓脆,逆變
同理我們可以給 T 加上 in 參數(shù):
interface IDemo<in T>
{
//僅將類型 T 用于輸入
string Method(T value);
}
public class Demo : IDemo<object>
{
//實(shí)現(xiàn)接口
public string Method (object value)
{
return value.ToString();
}
}
在Main函數(shù)中這樣寫:
IDemo<object> demoObj = new Demo();
IDemo<string> demoStr = demoObj;
這里可將 string Method (object value) 轉(zhuǎn)換為 string Method (string value)
即可將 IDemo<object> 類型轉(zhuǎn)換為 IDemo<string> 類型筛谚。
僅從泛型的類型上看,這是 “父->子” 的轉(zhuǎn)換狞贱,與第一節(jié)中提到的轉(zhuǎn)換方向相反刻获,稱之為“逆變”,有時(shí)也譯作“抗變”或“反變”瞎嬉。
四、總結(jié)
以上只討論了協(xié)變與逆變?cè)诜椒ㄖ械那闆r厚柳,其實(shí)在屬性中情況也相類似氧枣,不再說明。
可能大家也發(fā)現(xiàn)了别垮,所謂“協(xié)”與“逆”都是只是一種表象便监,其內(nèi)在本質(zhì)為同一過程。
“協(xié)變”與“逆變”中的“協(xié)”與“逆”表示泛型接口在將類型參數(shù)僅用于輸入或輸出的情況下碳想,其類型參數(shù)的隱式轉(zhuǎn)換所遵循的規(guī)律烧董。
協(xié)變
當(dāng)泛型接口類型僅用于輸出(使用關(guān)鍵詞 out),其類型參數(shù)隱式轉(zhuǎn)換所遵循的規(guī)律與對(duì)象引用的類型轉(zhuǎn)換規(guī)律相同胧奔,稱之為“協(xié)變”
逆變
當(dāng)泛型接口類型僅用于輸入(使用關(guān)鍵詞 in)逊移,其類型參數(shù)隱式轉(zhuǎn)換所遵循的規(guī)律與對(duì)象引用的類型轉(zhuǎn)換規(guī)律相反,稱之為“逆變”龙填、“抗變”或“反變”胳泉。