看過(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)得可靠。
新聞熱點(diǎn)
疑難解答
圖片精選
網(wǎng)友關(guān)注