国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁(yè) > 學(xué)院 > 開(kāi)發(fā)設(shè)計(jì) > 正文

[C#]淺談協(xié)變與逆變

2019-11-17 02:19:19
字體:
來(lái)源:轉(zhuǎn)載
供稿:網(wǎng)友

[C#]淺談協(xié)變與逆變

看過(guò)幾篇說(shuō)協(xié)變與逆變的博客,雖然都是正確無(wú)誤的,但是感覺(jué)都沒(méi)有說(shuō)得清晰明了,沒(méi)有切中要害。那么我也試著從我的理解角度來(lái)談一談協(xié)變與逆變吧。

什么是協(xié)變與逆變

MSDN的解釋:https://msdn.microsoft.com/zh-cn/library/dd799517.aspx

協(xié)變和逆變都是術(shù)語(yǔ),前者指能夠使用比原始指定的派生類型的派生程度更小(不太具體的)的類型,后者指能夠使用比原始指定的派生類型的派生程度更大(更具體的)的類型。泛型類型參數(shù)支持協(xié)變和逆變,可在分配和使用泛型類型方面提供更大的靈活性。

一開(kāi)始我總是分不清協(xié)變和逆變,因?yàn)镸SDN的解釋實(shí)在是嚴(yán)謹(jǐn)有余而易讀不足。其實(shí)從中文的字面上來(lái)理解這兩個(gè)概念就挺容易的了:

"協(xié)變"即"協(xié)調(diào)的轉(zhuǎn)變","逆變"即"逆向的轉(zhuǎn)變"。

為什么說(shuō)"能夠使用比原始指定的派生類型的派生程度更小(不太具體的)的類型"是協(xié)調(diào)的,而"能夠使用比原始指定的派生類型的派生程度更大(更具體的)的類型"是逆向的呢,看這兩行代碼:

object o = "";string s = (string) o;

string類型到object類型,也就是派生類到基類,是可以隱式轉(zhuǎn)換的,因?yàn)槿魏晤愋拖蚧惖霓D(zhuǎn)換都是類型安全的,所以認(rèn)為這一轉(zhuǎn)變是協(xié)調(diào)的。object類型到string類型,也就是基類到派生類,就只能是顯式轉(zhuǎn)換,因?yàn)閷?duì)象o的實(shí)際類型不一定是string,強(qiáng)制轉(zhuǎn)換不是類型安全的,所以認(rèn)為這一轉(zhuǎn)變是逆向的。

再看協(xié)變與逆變的常見(jiàn)場(chǎng)合:

IEnumerable<object> o = new List<string>();//協(xié)變Action<string> s = new Action<object>((arg)=>{...});//逆變

上例的泛型參數(shù)就是分別發(fā)生了協(xié)調(diào)的與逆向的轉(zhuǎn)變。

協(xié)變與逆變的作用對(duì)象

從定義中可以看到,協(xié)變與逆變都是針對(duì)的泛型參數(shù),而且

在.NET Framework 4中,Variant類型參數(shù)僅限于泛型接口和泛型委托類型。

為什么是接口和委托?先看IEnumerable<T>和Action<T>的聲明:

public interface IEnumerable<out T> : IEnumerable{    new IEnumerator<T> GetEnumerator();}public delegate void Action<in T>(T obj);

IEnumerable中的out關(guān)鍵字給泛型參數(shù)提供了協(xié)變的能力,Action中的in關(guān)鍵字給泛型參數(shù)提供了逆變的能力。這里的out和in是相對(duì)于誰(shuí)的入和出?不是相對(duì)于接口和委托,而是相對(duì)于方法體!看它們的實(shí)現(xiàn):

class MyEnumerable<T> : IEnumerable<T>{    public IEnumerator<T> GetEnumerator()    {        yield return default(T);    }    IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); }}Action<string> myAction = new Action<object>(    (o) =>    {        Console.WriteLine(o.ToString());    });

這樣是不是能看出來(lái)泛型參數(shù)是怎么入和出的了?那么接口和委托,它們和方法是什么關(guān)系呢,它們兩個(gè)之間又是什么關(guān)系,以下純屬個(gè)人理解:

接口類型定義了一組方法簽名,委托類型定義了一個(gè)方法結(jié)構(gòu)(方法簽名刨除方法名)。接口實(shí)例和委托實(shí)例都包含了一組方法入口。

綜上所述,協(xié)變與逆變的作用對(duì)象是方法體中的泛型參數(shù)。

為什么允許協(xié)變與逆變

協(xié)變和逆變都是類型發(fā)生了轉(zhuǎn)換,一旦涉及到類型轉(zhuǎn)換當(dāng)然就要想類型安全的問(wèn)題。協(xié)變和逆變之所以可以正常的運(yùn)轉(zhuǎn),就是因?yàn)檫@里所涉及到的所有類型轉(zhuǎn)換都是類型安全的!回頭看最開(kāi)始的四行代碼:

1 object o1 = "";//類型安全2 string s1 = (string) o1;//非類型安全3 IEnumerable<object> o2 = new List<string>();//協(xié)變4 Action<string> s2 = new Action<object>((arg)=>{...});//逆變

顯然第二行的object到string是非類型安全的,那為什么第四行的object到string就是類型安全的呢?結(jié)合上一個(gè)方法體的示例,來(lái)看這段代碼:

1 Action<List<int>> myAction = new Action<IList<int>>(2     (list) =>3     {4         Console.WriteLine(list.Count);5     });6 myAction(new List<int> {1, 2, 3});

第一行貌似是把IList轉(zhuǎn)換成了List,但是實(shí)際上是這樣的:第六行傳入的實(shí)參是一個(gè)List,進(jìn)入方法體,List被轉(zhuǎn)換成了IList,然后使用了IList的Count屬性。所以傳參的時(shí)候其實(shí)發(fā)生的是派生類到基類的轉(zhuǎn)換,自然也就是類型安全的了。

List<string>到IEnumerable<object>的協(xié)變其實(shí)也是類似的過(guò)程:

 1 IEnumerable<Delegate> myEnumerable = new List<Action> 2 { 3     new Action(()=>Console.WriteLine(1)), 4     new Action(()=>Console.WriteLine(2)), 5     new Action(()=>Console.WriteLine(3)), 6 }; 7 foreach (Delegate dlgt in myEnumerable) 8 { 9     dlgt.DynamicInvoke();10 }

實(shí)參是三個(gè)Action,調(diào)用的是Delegate的DynamicInvoke方法,完全的類型安全轉(zhuǎn)換。

最后想說(shuō)的是,所有死記硬背來(lái)的知識(shí),都遠(yuǎn)遠(yuǎn)不如充分理解的知識(shí)來(lái)得可靠。


發(fā)表評(píng)論 共有條評(píng)論
用戶名: 密碼:
驗(yàn)證碼: 匿名發(fā)表
主站蜘蛛池模板: 泾川县| 新沂市| 惠东县| 天祝| 闵行区| 吴川市| 五大连池市| 隆德县| 华蓥市| 丰台区| 桓台县| 平昌县| 江山市| 莆田市| 象山县| 平原县| 蚌埠市| 赤峰市| 绥中县| 桐乡市| 陵水| 合江县| 普陀区| 武汉市| 赤水市| 福鼎市| 无棣县| 永靖县| 新沂市| 吴江市| 平远县| 曲水县| 芒康县| 孟州市| 临桂县| 屏南县| 武鸣县| 临高县| 左云县| 永新县| 枣庄市|