我們先看下面一段程式:
/// <summary>
/// 父類
/// 作者:周公
/// 日期:2008-09-01
/// </summary>
public class Father
{
public void Run0()
{
Console.WriteLine("Father.Run0");
}
}
/// 子類
public class Son:Father
Console.WriteLine("Son.Run0");
}
class Program
static void Main(string[] args)
Father[] fatherList = new Father[2];
fatherList[0] = new Father();
fatherList[1] = new Son();
fatherList[0].Run0();
fatherList[1].Run0();
}
程式的運作結果是:
Father.Run0
稍微細心的朋友可能發現在Son類的Run0方法下面有一段棕色的波浪線,當我們把滑鼠放到該下劃線上時,會看到下面的提示(編譯程式時在程式的“輸出”視窗也能看到這個警告):
“MethodDemo.Son.Run0()”隐藏了繼承的成員“MethodDemo.Father.Run0()”。如果是有意隐藏,請使用關鍵字new。
如圖:
然後我們再來第二個版本的Run方法,我們稱之為Run1(),,與第一個版本的差別是在子類的同名同參(方法名相同,參數個數和參數順序相同,下同)方法前面加上了一個new關鍵字,代碼如下:
/// 作者:周公
/// 日期:2008-09-01
/// </summary>
public void Run1()
Console.WriteLine("Father.Run1");
public new void Run1()
Console.WriteLine("Son.Run1");
fatherList[0].Run1();
fatherList[1].Run1();
運作結果如下:
Father.Run1
我們發現加上new關鍵字之後,程式的運作結果沒有發生改變。也就是在C#中,如果在子類中有與父類同名同參的方法時,C#會隐式幫你在子類的方法前面添加一個new關鍵字。
我們再寫第三個版本的Run方法,即Run2(),與第一個版本不同的是父類的Run2()方面前面加了一個virtual關鍵字,表示這是一個虛方法,子類的Run2()除了與第一個版本号不同之外(Run0()改成Run2())沒有什麼不同。
程式代碼如下:
public virtual void Run2()
Console.WriteLine("Father.Run2");
public void Run2()
Console.WriteLine("Son.Run2");
fatherList[0].Run2();
fatherList[1].Run2();
我們看看程式的運作效果:
Father.Run2
程式運作效果與第一個仍然沒有什麼差別,不過這次子類(Son) 的Run2()方法又出現了與Run方法的第一個版本(Run0())一樣的警 告:“MethodDemo.Son.Run2()”将隐藏繼承的成 員“MethodDemo.Father.Run2()”。若要使目前成員重寫該實作,請添加關鍵字override。否則,添 加關鍵字new。
我們再寫第四個版本的Run 方法,我們稱之為Run3(),這次父類中Run3()的修飾符與第三個版本相比沒有變化(依然是virtual,虛方法),而子類Son中的 Run3()方法與第三個版本相比多了一個override修飾符(這次我們熟悉的那個棕色的波浪線警告沒有了)。代碼如下:
public virtual void Run3()
Console.WriteLine("Father.Run3");
public override void Run3()
Console.WriteLine("Son.Run3");
fatherList[0].Run3();
fatherList[1].Run3();
程式的運作結果如下:
Father.Run3
Son.Run3
這次我們發現程式的運作結果與前面三次不一樣了,這次盡管我們聲明的對象類型都是Father類(Father數組裝的自然都是Father類型的引用),但是因為執行個體化數組中第二個元素的時候調用了Son類的構造函數,也就是執行個體化了一個Father類的子類(我們知道子類可以當作父類來看待,他們之間是is a的關系,反之則不行),<b>也就是說fatherList數組中的第二個元素的引用類型是Father類型,但它的執行個體類型确實Son類型</b>。而在運作的時候,并不是根據我們的引用類型(引用類型是Father類型的)去調用該引用類型的方法,而是調用該引用類型所指向的執行個體的方法。
為什麼會發生這些現象呢?這裡要提到兩個概念:早綁定(early binding)和晚綁定(Late binding)。這個術語出現在存在繼承關系的基類和派生類中,它們同時定義了一個同名同參的方法。
早綁定:在編譯的時候就已經确定了将來程式運作基類或是派生類的哪個方法。在編譯代碼的時候根據引用類型就決定了運作該引用類型中定義的方法,即基類的方法。這種方法運作效率高。
晚綁定:隻有在運作的時候才能決定運作基類或者派生類中的哪個方法。運作的時候将根據該實際類型而不是引用類型來調用相關方法,即取決于我們new了什麼樣對象。也就是即使我們new一個Father類的子類Son的執行個體,而不管我們是用Father類的引用指向這個Son的執行個體,方法調用的時候依然是調用Son的方法,而不是Father類的同名方法。
如我們上面所見,為了實作晚綁定,C# 引入了兩個關鍵詞virtual和override。和Java中不同,Java中一切方法都是虛方法,也就是在運作的時候,JVM會自動檢測該引用的類 型與實際類型是否一緻(無論如何,該引用類型與實際類型之間存在着相等或者繼承關系,這樣才滿足is a的關系),如果一緻執行該類型中定義的方法;如果不一緻則會檢查該引用的實際類型是否具有同名同參方法,如果有則運作該實際類型的同名同參方法。這樣會 帶來一個問題:每次運作的時候都會進行類型檢查,這樣會帶來一定的性能消耗。而在C#中一切方法如果沒有顯示指明,都是非虛的。對于非虛的方法,CLR運作的時候并不會進行類型檢查,而是直接運作該引用的類型中所定義的方法,即使這個引用所指向的實際類型是該引用類型的派生類,并且在派生類中存在着同名同參的方法,也不會運作派生類中定義的方法。這時,派生類中的方法隐藏了基類中的方法。
但是如果在基類中顯示聲明方法為虛方法,那麼CLR在運作的時候會進行類型檢查,如果發現引用類型和實際的對象類型不一緻,就會檢查派生類中是否覆寫(override)了基類中的方法,如果是,則會運作派生類中的方法,而不是引用類型中的方法。
本文轉自周金橋51CTO部落格,原文連結: http://blog.51cto.com/zhoufoxcn/162950,如需轉載請自行聯系原作者