对协变和逆变的简单理解
畢業(yè)快一年了,邊工作邊學(xué)習(xí),雖說(shuō)對(duì).net不算精通,但也算入門了,但一直以來(lái)對(duì)協(xié)變和逆變這個(gè)概念不是太了解,上學(xué)時(shí)候mark了一些文章,今天回過頭看感覺更糊涂了,真驗(yàn)證本人一句口頭禪“知道的越多,知道的越少”。看到最后實(shí)在亂了,就干脆裝糊涂好了,本人也算半個(gè)陰謀論者,在編程語(yǔ)言這方面當(dāng)我實(shí)在沒法吃透一個(gè)語(yǔ)法的時(shí)候,我就歸咎于編譯器這個(gè)幕后黑手。我們看下面兩個(gè)類Derived派生自Base:
public class Base { }public class Derived:Base { }我們都這知道下面這兩行代碼,第一行能編譯通過,第二行則無(wú)法編譯通過:
Base b=new Derived(); Derived d = new Base();當(dāng)我們嘗試編譯第二行代碼的時(shí)候,編譯器會(huì)提示我們?nèi)鄙僖粋€(gè)顯示類型轉(zhuǎn)換。那我們加上強(qiáng)制類型轉(zhuǎn)換后自然就沒問題了。
Derived d = (Derived)new Base();Why?其實(shí)原因很簡(jiǎn)單,因?yàn)镃#語(yǔ)言規(guī)范就是這樣的,編譯器就是這么處理的。這有點(diǎn)像宇宙學(xué)中的“人擇原理”,當(dāng)我弄不清楚一個(gè)問題我就放空自己。當(dāng)然隨著人類慢慢探索,對(duì)宇宙的了解越來(lái)越多,宇宙是現(xiàn)在這樣是有它的道理的,編譯器這樣處理也是有它道理的。下面說(shuō)下自己對(duì)上面為什么子類對(duì)象能賦值給父類變量而父類對(duì)象不能賦給子類變量的粗俗理解(不談多態(tài))。
每個(gè)對(duì)象本質(zhì)上都是內(nèi)存中的一塊地址空間,當(dāng)然不同對(duì)象占用的地址空間不同。我們聲明一個(gè)對(duì)象后Base ?b=new Derived() ,怎么訪問這塊地址空間呢?當(dāng)然就是通過那個(gè)“變量”b。變量的類型就決定了這個(gè)變量能“看到”多大的地方,變量就是查看對(duì)象的一雙“眼睛”。子類繼承自父類,子類的對(duì)象比父類的對(duì)象要大些。
?
父類對(duì)象變量的“視角”要比子類對(duì)象變量“視角”小。當(dāng)我們把子類對(duì)象賦個(gè)父類變量的時(shí)候:
Base b=new Derived();?
變量b只會(huì)看到它能看到的東西,換句話說(shuō)指針不會(huì)訪問到未知的區(qū)域,所以這種類型的隱式轉(zhuǎn)換是安全的,編譯器允許這么做。
反過來(lái)如果把一個(gè)父類對(duì)象賦給子類的變量:
Derived d = new Base();因?yàn)樽宇愖兞康囊曇胺秶^了父類對(duì)象的大小,就會(huì)看到了不該看到了,換句話說(shuō),指針能訪問到不該訪問的區(qū)域,這被認(rèn)為是不安全的,因此編譯器不允許這么做。
那么這和協(xié)變和逆變又有什么關(guān)系呢?個(gè)人認(rèn)為協(xié)變逆變不過是一種隱式類型轉(zhuǎn)換,.net4.0通過in和out關(guān)鍵字保證了在泛型接口和委托上對(duì)這種安全的允許的隱式轉(zhuǎn)換的支持。下面以委托做簡(jiǎn)單的說(shuō)明。
先看協(xié)變:
public delegate T Function<out T>(); public delegate void Operate<in T>(T instance); static void Main(string[] args) {Function<Derived> funDer = new Function<Derived>(() => { return new Derived(); });Function<Base> funBase = funDer;Base b = funBase.Invoke(); }首先我想說(shuō)明下,之前看網(wǎng)上有人說(shuō)Function<Base> funBase = funDer;這句是“子類對(duì)象賦值給父類的變量(這里幸好是委托,如果是接口可能更容易這么覺得),父類調(diào)用子類的方法,體現(xiàn)了多態(tài)。”因此就得出觀點(diǎn):“協(xié)變體現(xiàn)了多態(tài)性”。個(gè)人認(rèn)為這里根本不存在多態(tài)的概念,funBase和funDer根本就不是父子類的關(guān)系何來(lái)多態(tài),相反這里體現(xiàn)的面相對(duì)象的另一個(gè)特性繼承。本質(zhì)上就是上面提到的Base b = funBase.Invoke();這里可以安全的進(jìn)行從Derived到Base的轉(zhuǎn)換,b不會(huì)看到不該看到的。
再來(lái)看下逆變:
Operate<Base> opBase = new Operate<Base>(x => { Console.WriteLine(x.ToString()); }); Operate<Derived> opDer = opBase;opDer.Invoke(new Derived());同樣有人說(shuō)這里Operate<Derived> opDer = opBase;是“父類變量賦值給子類變量,是4.0種出現(xiàn)的新的特性,以前沒見過。”事實(shí)上呢?事實(shí)上這里才真正體現(xiàn)了多態(tài)性。x => { Console.WriteLine(x.ToString());這里x只會(huì)以父類的視角去看傳遞給該方法的參數(shù),只會(huì)看到子類中它能看到的(包括重載的方法),這不正是多態(tài)的體現(xiàn)嗎?當(dāng)然也是因?yàn)榉仙厦嫖姨岬降念愋椭g安全的隱式轉(zhuǎn)換,所以編譯器自然支持這種“逆變”。
泛型接口中的協(xié)變和逆變理解起來(lái)更難點(diǎn)(一個(gè)原因我想是更容易讓人跟傳統(tǒng)的繼承、多態(tài)聯(lián)系在一起了),但本質(zhì)上是一樣的。
以上就是我個(gè)人對(duì)協(xié)變和逆變的一些膚淺的理解。其實(shí)很多人我想都被這兩個(gè)忒專業(yè)的術(shù)語(yǔ)嚇到了,如果真的理解不了那就暫且不去了解,F1看MSDN:
Covariance permits a method to have return type that is more derived than that defined in the delegate. Contravariance permits a method that has parameter types that are less derived than those in the delegate type. —— MSDN
協(xié)變是大轉(zhuǎn)小(具體轉(zhuǎn)抽象)1.子類轉(zhuǎn)基類
2.實(shí)現(xiàn)轉(zhuǎn)接口
逆變是小傳大
1.基類轉(zhuǎn)子類
2.接口轉(zhuǎn)實(shí)現(xiàn)
顧名思義:協(xié)變是和諧的,逆變是大逆不道的。
一般逆變都應(yīng)該強(qiáng)制轉(zhuǎn)換,但是泛型不同。泛型實(shí)例本質(zhì)上不是父子關(guān)系,而是借助函數(shù)的參數(shù),和返回類型來(lái)做文章。
協(xié)變
1.返回類型大轉(zhuǎn)小
逆變
1.參數(shù)小轉(zhuǎn)大
注意,參數(shù)小轉(zhuǎn)大和其他不同的在于,使用者是小參數(shù)。
而其他逆變的使用者是”大“的類型。
Derived derived = (Derived)base;//
derived.func();通過大類型使用小對(duì)象。
Action<Base> baseFunc = (b)=>b.func();//通過小類型使用大對(duì)象
Action<Derived> derivedFunc = baseFunc;//逆變的形式表象
derivedFunc(derived); //調(diào)用((Base)derived).func();即通過小類型使用大對(duì)象。
因此,泛型逆變,雖然看上去是”逆“的,但實(shí)質(zhì)使用的是協(xié)變的邏輯。
出處:http://www.cnblogs.com/magialmoon/archive/2013/04/13/3019270.html
轉(zhuǎn)載于:https://www.cnblogs.com/zjoch/p/6572059.html
總結(jié)
以上是生活随笔為你收集整理的对协变和逆变的简单理解的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: MAC通过SSH使用PEM文件登录
- 下一篇: poj3666 Making the G