C#泛型的逆變協(xié)變之個(gè)人理解
一般來(lái)說(shuō), 泛型的作用就類(lèi)似一個(gè)占位符, 或者說(shuō)是一個(gè)參數(shù), 可以讓我們把類(lèi)型像參數(shù)一樣進(jìn)行傳遞, 盡可能地復(fù)用代碼。
我有個(gè)朋友, 在使用的過(guò)程中發(fā)現(xiàn)一個(gè)問(wèn)題
IFace<object> item = new Face<string>(); // CS0266
public interface IFace<T>
{
string Print(T input);
}
public class Face<T> : IFace<T>
{
public string Print(T input) => input.ToString();
}
Q: string 明明是 object 的子類(lèi), 為啥這樣賦值會(huì)報(bào)錯(cuò)呢???
A: 因?yàn)?nbsp;Face<string> 實(shí)現(xiàn)的是 IFace<string>, 而 IFace<string> 并不是 IFace<object> 的子類(lèi)
Q: 但是 string 是 object 的子類(lèi)啊, IFace<string> 可不就是 IFace<object> 嗎?
A: 如果只論接口定義, 看起來(lái)確實(shí)是這樣的, 但是你要看內(nèi)部實(shí)現(xiàn)的方法, IFace<string> 的 Print 方法參數(shù)是 string, 但是 IFace<object> 的 Print 參數(shù)是 object, 如果上面的賦值可以成立, 就意味著允許 Print(string input) 方法傳遞任意類(lèi)型的對(duì)象, 這樣明顯是有問(wèn)題的
Q: 但是我曾經(jīng)看到過(guò) IEnumerable<object> list = new List<string>(); 這個(gè)為什么就可以
A: 這就要講到C#泛型里的逆變協(xié)變了
Q: 細(xì)嗦細(xì)嗦
逆變協(xié)變
C#泛型中的逆變(in)協(xié)變(out)對(duì)于不常自定義泛型的開(kāi)發(fā)來(lái)說(shuō)(可能)是個(gè)很難理解的概念, 簡(jiǎn)單來(lái)說(shuō)其表現(xiàn)形式如下
逆變(in): I<子類(lèi)> = I<父類(lèi)>協(xié)變(out): I<父類(lèi)> = I<子類(lèi)>
上面例子中提到的 IEnumerable<object> list = new List<string>(); 體現(xiàn)的是協(xié)變, 符合一般直覺(jué), 整體上看起來(lái)就像是將子類(lèi)賦值給基類(lèi)
轉(zhuǎn)到 IEnumerable<> 的定義, 我們可以看到
public interface IEnumerable<out T> : IEnumerable
{
new IEnumerator<T> GetEnumerator();
}
泛型 T 之前加了協(xié)變的關(guān)鍵詞 out, 代表支持協(xié)變, 可以進(jìn)行符合直覺(jué)且和諧的轉(zhuǎn)化
前編中提到的代碼例子不適用并且也不能改造成協(xié)變, 只適合使用逆變
相比于符合直覺(jué)且和諧的協(xié)變, 逆變是不符合直覺(jué)并且別扭的
IFace<string> item = new Face<object>();
public interface IFace<in T>
{
string Print(T input);
}
public class Face<T> : IFace<T>
{
public string Print(T input) => input.ToString();
}
這是一個(gè)逆變的例子, 與協(xié)變相似, 需要在泛型 T 之前加上關(guān)鍵詞 in
對(duì)比上方的協(xié)變, 逆變看起來(lái)就像是將基類(lèi)賦值給子類(lèi), 但這其實(shí)符合里氏代換的
當(dāng)我們調(diào)用 item.Print 時(shí), 看起來(lái)允許傳入的參數(shù)為 string 類(lèi)型, 而實(shí)際上最終調(diào)用的 Face<object>.Print 是支持 object 的, 傳入 string 類(lèi)型的參數(shù)沒(méi)有任何問(wèn)題
逆變協(xié)變的作用
逆變(in)協(xié)變(out)的作用就是擴(kuò)展泛型的用法, 幫助開(kāi)發(fā)者更好地復(fù)用代碼, 同時(shí)通過(guò)約束限制可能會(huì)出現(xiàn)的破壞類(lèi)型安全的操作
逆變協(xié)變的限制
雖然上面講了逆變(in)協(xié)變(out)看起來(lái)是什么樣的, 但我的那個(gè)朋友還是有些疑問(wèn)
Q: 那我什么時(shí)候可以用逆變, 什么時(shí)候可以用協(xié)變, 這兩個(gè)東西用起來(lái)有什么限制?
A: 簡(jiǎn)單來(lái)說(shuō), 有關(guān)泛型輸入的用逆變, 關(guān)鍵詞是in, 有關(guān)泛型輸出的用協(xié)變, 關(guān)鍵詞是out, 如果接口中既有輸入又有輸出, 就不能用逆變協(xié)變
Q: 為什么這兩個(gè)不能同時(shí)存在?
A: 協(xié)變的表現(xiàn)形式為將子類(lèi)賦值給基類(lèi), 當(dāng)進(jìn)行輸出相關(guān)操作時(shí), 輸出的對(duì)象類(lèi)型為基類(lèi), 是將子類(lèi)轉(zhuǎn)為基類(lèi), 你可以說(shuō)子類(lèi)是基類(lèi);逆變的表現(xiàn)形式為將基類(lèi)賦值給子類(lèi), 當(dāng)進(jìn)行輸入相關(guān)操作時(shí), 輸入的對(duì)象為子類(lèi), 是將子類(lèi)轉(zhuǎn)為基類(lèi), 這個(gè)時(shí)候你也可以說(shuō)基類(lèi)是子類(lèi);
如果同時(shí)支持逆變協(xié)變, 若先進(jìn)行子類(lèi)賦值給基類(lèi)的操作, 此時(shí)輸出的是基類(lèi), 子類(lèi)轉(zhuǎn)為基類(lèi)并不會(huì)有什么問(wèn)題, 但進(jìn)行輸入操作時(shí)就是在將基類(lèi)轉(zhuǎn)為子類(lèi), 此時(shí)是無(wú)法保證類(lèi)型安全的;
Q: 聽(tīng)不懂, 能不能舉個(gè)例子給我?
A: 假設(shè) IEnumerable<> 同時(shí)支持逆變協(xié)變, IEnumerable<object> list = new List<string>();進(jìn)行賦值后, list中實(shí)際保存的類(lèi)型是string, item.First()的輸出類(lèi)型為object, 實(shí)際類(lèi)型是string, 此時(shí)說(shuō)string是object沒(méi)有任何問(wèn)題, 協(xié)變可以正常發(fā)揮作用;
但是如果支持了逆變, 假設(shè)我們進(jìn)行輸入類(lèi)型的操作, item.Add() 允許的參數(shù)類(lèi)型為 object, 可以是任意類(lèi)型, 但是實(shí)際上支持string類(lèi)型, 此時(shí)的object絕無(wú)可能是string
Q: 好像聽(tīng)懂了一點(diǎn)了, 我以后慢慢琢磨吧
兩者的限制簡(jiǎn)單總結(jié)就是
輸入的用逆變
??????????????輸出的用協(xié)變
相關(guān)文章
C#通過(guò)標(biāo)簽軟件Bartender的ZPL命令打印條碼
這篇文章介紹了C#通過(guò)標(biāo)簽軟件Bartender的ZPL命令打印條碼,對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-01-01
c# 動(dòng)態(tài)加載dll文件,并實(shí)現(xiàn)調(diào)用其中的簡(jiǎn)單方法
下面小編就為大家?guī)?lái)一篇c# 動(dòng)態(tài)加載dll文件,并實(shí)現(xiàn)調(diào)用其中的簡(jiǎn)單方法。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-01-01
C#實(shí)現(xiàn)把圖片轉(zhuǎn)換成二進(jìn)制以及把二進(jìn)制轉(zhuǎn)換成圖片的方法示例
這篇文章主要介紹了C#實(shí)現(xiàn)把圖片轉(zhuǎn)換成二進(jìn)制以及把二進(jìn)制轉(zhuǎn)換成圖片的方法,結(jié)合具體實(shí)例形式分析了基于C#的圖片與二進(jìn)制相互轉(zhuǎn)換以及圖片保存到數(shù)據(jù)庫(kù)的相關(guān)操作技巧,需要的朋友可以參考下2017-06-06
C#中Write()和WriteLine()的區(qū)別分析
這篇文章主要介紹了C#中Write()和WriteLine()的區(qū)別分析,需要的朋友可以參考下2020-11-11
C#?崩潰異常中研究頁(yè)堆布局的詳細(xì)過(guò)程
最近遇到一位朋友的程序崩潰,發(fā)現(xiàn)崩潰點(diǎn)在富編輯器 msftedit 上,這個(gè)不是重點(diǎn),重點(diǎn)在于發(fā)現(xiàn)他已經(jīng)開(kāi)啟了 頁(yè)堆,由于 頁(yè)堆 和 NT堆 的內(nèi)存布局完全不一樣,這一篇結(jié)合我的了解以及 windbg 驗(yàn)證來(lái)系統(tǒng)的介紹下 頁(yè)堆,需要的朋友可以參考下2022-10-10

