我們先看下面一段程序:
程序的運行結果是:
Father.Run0
Father.Run0
稍微細心的朋友可能發(fā)現在Son類(lèi)的Run0方法下面有一段棕色的波浪線(xiàn),當我們把鼠標放到該下劃線(xiàn)上時(shí),會(huì )看到下面的提示(編譯程序時(shí)在程序的“輸出”窗口也能看到這個(gè)警告):
“MethodDemo.Son.Run0()”隱藏了繼承的成員“MethodDemo.Father.Run0()”。如果是有意隱藏,請使用關(guān)鍵字new。
如圖:
然后我們再來(lái)第二個(gè)版本的Run方法,我們稱(chēng)之為Run1(),,與第一個(gè)版本的區別是在子類(lèi)的同名同參(方法名相同,參數個(gè)數和參數順序相同,下同)方法前面加上了一個(gè)new關(guān)鍵字,代碼如下:
運行結果如下:
Father.Run1
Father.Run1
我們發(fā)現加上new關(guān)鍵字之后,程序的運行結果沒(méi)有發(fā)生改變。也就是在C#中,如果在子類(lèi)中有與父類(lèi)同名同參的方法時(shí),C#會(huì )隱式幫你在子類(lèi)的方法前面添加一個(gè)new關(guān)鍵字。
我們再寫(xiě)第三個(gè)版本的Run方法,即Run2(),與第一個(gè)版本不同的是父類(lèi)的Run2()方面前面加了一個(gè)virtual關(guān)鍵字,表示這是一個(gè)虛方法,子類(lèi)的Run2()除了與第一個(gè)版本號不同之外(Run0()改成Run2())沒(méi)有什么不同。
程序代碼如下:
我們看看程序的運行效果:
Father.Run2
Father.Run2
程序運行效果與第一個(gè)仍然沒(méi)有什么區別,不過(guò)這次子類(lèi)(Son)的Run2()方法又出現了與Run方法的第一個(gè)版本(Run0())一樣的警告:“MethodDemo.Son.Run2()”將隱藏繼承的成員“MethodDemo.Father.Run2()”。若要使當前成員重寫(xiě)該實(shí)現,請添加關(guān)鍵字override。否則,添加關(guān)鍵字new。
我們再寫(xiě)第四個(gè)版本的Run方法,我們稱(chēng)之為Run3(),這次父類(lèi)中Run3()的修飾符與第三個(gè)版本相比沒(méi)有變化(依然是virtual,虛方法),而子類(lèi)Son中的Run3()方法與第三個(gè)版本相比多了一個(gè)override修飾符(這次我們熟悉的那個(gè)棕色的波浪線(xiàn)警告沒(méi)有了)。代碼如下:
程序的運行結果如下:
Father.Run3
Son.Run3
這次我們發(fā)現程序的運行結果與前面三次不一樣了,這次盡管我們聲明的對象類(lèi)型都是Father類(lèi)(Father數組裝的自然都是Father類(lèi)型的引用),但是因為實(shí)例化數組中第二個(gè)元素的時(shí)候調用了Son類(lèi)的構造函數,也就是實(shí)例化了一個(gè)Father類(lèi)的子類(lèi)(我們知道子類(lèi)可以當作父類(lèi)來(lái)看待,他們之間是is a的關(guān)系,反之則不行),也就是說(shuō)fatherList數組中的第二個(gè)元素的引用類(lèi)型是Father類(lèi)型,但它的實(shí)例類(lèi)型確實(shí)Son類(lèi)型。而在運行的時(shí)候,并不是根據我們的引用類(lèi)型(引用類(lèi)型是Father類(lèi)型的)去調用該引用類(lèi)型的方法,而是調用該引用類(lèi)型所指向的實(shí)例的方法。
為什么會(huì )發(fā)生這些現象呢?這里要提到兩個(gè)概念:早綁定(early binding)和晚綁定(Late binding)。這個(gè)術(shù)語(yǔ)出現在存在繼承關(guān)系的基類(lèi)和派生類(lèi)中,它們同時(shí)定義了一個(gè)同名同參的方法。
早綁定:在編譯的時(shí)候就已經(jīng)確定了將來(lái)程序運行基類(lèi)或是派生類(lèi)的哪個(gè)方法。在編譯代碼的時(shí)候根據引用類(lèi)型就決定了運行該引用類(lèi)型中定義的方法,即基類(lèi)的方法。這種方法運行效率高。
晚綁定:只有在運行的時(shí)候才能決定運行基類(lèi)或者派生類(lèi)中的哪個(gè)方法。運行的時(shí)候將根據該實(shí)際類(lèi)型而不是引用類(lèi)型來(lái)調用相關(guān)方法,即取決于我們new了什么樣對象。也就是即使我們new一個(gè)Father類(lèi)的子類(lèi)Son的實(shí)例,而不管我們是用Father類(lèi)的引用指向這個(gè)Son的實(shí)例,方法調用的時(shí)候依然是調用Son的方法,而不是Father類(lèi)的同名方法。
如我們上面所見(jiàn),為了實(shí)現晚綁定,C#引入了兩個(gè)關(guān)鍵詞virtual和override。和Java中不同,Java中一切方法都是虛方法,也就是在運行的時(shí)候,JVM會(huì )自動(dòng)檢測該引用的類(lèi)型與實(shí)際類(lèi)型是否一致(無(wú)論如何,該引用類(lèi)型與實(shí)際類(lèi)型之間存在著(zhù)相等或者繼承關(guān)系,這樣才滿(mǎn)足is a的關(guān)系),如果一致執行該類(lèi)型中定義的方法;如果不一致則會(huì )檢查該引用的實(shí)際類(lèi)型是否具有同名同參方法,如果有則運行該實(shí)際類(lèi)型的同名同參方法。這樣會(huì )帶來(lái)一個(gè)問(wèn)題:每次運行的時(shí)候都會(huì )進(jìn)行類(lèi)型檢查,這樣會(huì )帶來(lái)一定的性能消耗。而在C#中一切方法如果沒(méi)有顯示指明,都是非虛的。對于非虛的方法,CLR運行的時(shí)候并不會(huì )進(jìn)行類(lèi)型檢查,而是直接運行該引用的類(lèi)型中所定義的方法,即使這個(gè)引用所指向的實(shí)際類(lèi)型是該引用類(lèi)型的派生類(lèi),并且在派生類(lèi)中存在著(zhù)同名同參的方法,也不會(huì )運行派生類(lèi)中定義的方法。這時(shí),派生類(lèi)中的方法隱藏了基類(lèi)中的方法。
但是如果在基類(lèi)中顯示聲明方法為虛方法,那么CLR在運行的時(shí)候會(huì )進(jìn)行類(lèi)型檢查,如果發(fā)現引用類(lèi)型和實(shí)際的對象類(lèi)型不一致,就會(huì )檢查派生類(lèi)中是否覆蓋(override)了基類(lèi)中的方法,如果是,則會(huì )運行派生類(lèi)中的方法,而不是引用類(lèi)型中的方法。
有興趣的朋友還可以看看本人的另一篇文章:《談?wù)凜#中的三個(gè)關(guān)鍵詞new , virtual , override》
聯(lián)系客服