C# 語言經過專門設計,以便不同庫中的基類與派生類之間的版本控制可以不斷向前發展,同時保持向后兼容。這具有多方面的意義。例如,這意味著在基類中引入與派生類中的某個成員具有相同名稱的新成員在 C# 中是完全支持的,不會導致意外行為。它還意味著類必須顯式聲明某方法是要重寫一個繼承方法,還是一個隱藏具有類似名稱的繼承方法的新方法。
在 C# 中,派生類可以包含與基類方法同名的方法。
基類方法必須定義為 virtual。
可以從派生類中使用 base 關鍵字調用基類方法。
override、virtual 和 new 關鍵字還可以用于屬性、索引器和事件中。
默認情況下,C# 方法為非虛方法。如果某個方法被聲明為虛方法,則繼承該方法的任何類都可以實現它自己的版本。若要使方法成為虛方法,必須在基類的方法聲明中使用 virtual 修飾符。然后,派生類可以使用 override 關鍵字重寫基虛方法,或使用 new 關鍵字隱藏基類中的虛方法。如果 override 關鍵字和 new 關鍵字均未指定,編譯器將發出警告,并且派生類中的方法將隱藏基類中的方法。
為了在實踐中演示上述情況,我們暫時假定公司 A 創建了一個名為 GraphicsClass 的類,您的程序將使用此類。 GraphicsClass 如下所示:
class GraphicsClass{ public virtual void DrawLine() { } public virtual void DrawPoint() { }}
您的公司使用此類,并且您在添加新方法時將其用來派生自己的類:
class YourDerivedGraphicsClass : GraphicsClass{ public void DrawRectangle() { }}
您的應用程序運行正常,直到公司 A 發布了 GraphicsClass 的新版本,類似于下面的代碼:
class GraphicsClass{ public virtual void DrawLine() { } public virtual void DrawPoint() { } public virtual void DrawRectangle() { }}
現在,GraphicsClass 的新版本中包含一個名為 DrawRectangle 的方法。開始時,沒有出現任何問題。新版本仍然與舊版本保持二進制兼容。已經部署的任何軟件都將繼續正常工作,即使新類已安裝到這些軟件所在的計算機系統上。在您的派生類中,對方法 DrawRectangle 的任何現有調用將繼續引用您的版本。
但是,一旦您使用 GraphicsClass 的新版本重新編譯應用程序,就會收到來自編譯器的警告 CS0108。此警告提示您必須考慮希望 DrawRectangle 方法在應用程序中的工作方式。
如果您希望自己的方法重寫新的基類方法,請使用 override 關鍵字:
class YourDerivedGraphicsClass : GraphicsClass{ public override void DrawRectangle() { }}
override 關鍵字可確保派生自 YourDerivedGraphicsClass 的任何對象都將使用 DrawRectangle 的派生類版本。派生自 YourDerivedGraphicsClass 的對象仍可以使用基關鍵字訪問 DrawRectangle 的基類版本:
base.DrawRectangle();
如果您不希望自己的方法重寫新的基類方法,則需要注意以下事項。為了避免這兩個方法之間發生混淆,可以重命名您的方法。這可能很耗費時間且容易出錯,而且在某些情況下并不可行。但是,如果您的項目相對較小,則可以使用 Visual Studio 的重構選項來重命名方法。
或者,也可以通過在派生類定義中使用關鍵字 new 來防止出現該警告:
class YourDerivedGraphicsClass : GraphicsClass{ public new void DrawRectangle() { }}
使用 new 關鍵字可告訴編譯器您的定義將隱藏基類中包含的定義。這是默認行為。
重寫和方法選擇
當在類中指定方法時,如果有多個方法與調用兼容(例如,存在兩種同名的方法,并且其參數與傳遞的參數兼容),則 C# 編譯器將選擇最佳方法進行調用。下面的方法將是兼容的:
public class Derived : Base{ public override void DoWork(int param) { } public void DoWork(double param) { }}
在 Derived 的一個實例中調用 DoWork 時,C# 編譯器將首先嘗試使該調用與最初在 Derived 上聲明的 DoWork 版本兼容。重寫方法不被視為是在類上進行聲明的,而是在基類上聲明的方法的新實現。僅當 C# 編譯器無法將方法調用與 Derived 上的原始方法匹配時,它才嘗試將該調用與具有相同名稱和兼容參數的重寫方法匹配。例如:
int val = 5;Derived d = new Derived();d.DoWork(val); // Calls DoWork(double).
由于變量 val 可以隱式轉換為 double 類型,因此 C# 編譯器將調用 DoWork(double),而不是 DoWork(int)。有兩種方法可以避免此情況。首先,避免將新方法聲明為與虛方法同名。其次,可以通過將 Derived 的實例強制轉換為 Base 來使 C# 編譯器搜索基類方法列表,從而使其調用虛方法。由于是虛方法,因此將調用 Derived 上的 DoWork(int) 的實現。例如:
((Base)d).DoWork(val); // Calls DoWork(int) on Derived.
何時使用 Override 和 New 關鍵字
在 C# 中,派生類中方法的名稱可與基類中方法的名稱相同。可通過使用 new 和 override 關鍵字指定方法互動的方式。 override 修飾符 extends 基類方法,且 new 修飾符將其“隱藏”起來。這種區別在本主題中的示例顯示出來。
在控制臺應用程序中,聲明下面的 BaseClass 和 DerivedClass 兩個類. DerivedClass 繼承自 BaseClass。
class BaseClass{ public void Method1() { Console.WriteLine("Base - Method1"); }}class DerivedClass : BaseClass{ public void Method2() { Console.WriteLine("Derived - Method2"); }}
在 Main 方法中,聲明變量 bc、dc 和 bcdc。
由于 bc 和 bcdc 具有類型 BaseClass,因此,除非您使用強制轉換,否則它們只會直接訪問 Method1。變量 dc 可以訪問 Method1 和 Method2。下面的代碼演示這些關系。
class Program{ static void Main(string[] args) { BaseClass bc = new BaseClass(); DerivedClass dc = new DerivedClass(); BaseClass bcdc = new DerivedClass(); bc.Method1(); dc.Method1(); dc.Method2(); bcdc.Method1(); } // Output: // Base - Method1 // Base - Method1 // Derived - Method2 // Base - Method1}
接下來,將以下 Method2 方法添加到 BaseClass。此方法的簽名與 DerivedClass 中 Method2 方法的簽名相匹配。
public void Method2(){ Console.WriteLine("Base - Method2");}
由于 BaseClass 現在有 Method2 方法,因此可以為 BaseClass 變量 bc 和 bcdc 添加第二個調用語句,如下面的代碼所示。
bc.Method1();bc.Method2();dc.Method1();dc.Method2();bcdc.Method1();bcdc.Method2();
當生成項目時,您將看到在 BaseClass 中添加 Method2 方法將引發警告。警告提示,DerivedClass 中的 Method2 方法將 Method2 方法隱藏在 BaseClass 中。如果要獲得該結果,則建議您使用 Method2 定義中的 new 關鍵字?;蛘撸梢灾孛?Method2 方法之一來解決警告,但這始終不實用。
在添加 new 之前,運行該程序以查看其他調用語句生成的輸出。顯示以下結果。
輸出:
Base - Method1Base - Method2Base - Method1Derived - Method2Base - Method1Base - Method2
new 關鍵字可以保留生成輸出的關系,但它將取消警告。具有 BaseClass 類型的變量繼續訪問 BaseClass 成員,具有 DerivedClass 類型的變量首先繼續訪問 DerivedClass 中的成員,然后再考慮從 BaseClass 繼承的成員.
要禁止顯示警告,請向 DerivedClass 中的 Method2 定義添加 new 修飾符,如下面的示例所示:可在 public 前后添加修飾符。
public new void Method2(){ Console.WriteLine("Derived - Method2");}
再次運行該程序以確認沒有更改輸出。還確認警告不再出現。通過使用 new,您斷言您了解它修改的成員將隱藏從基類繼承的成員。關于通過繼承隱藏名稱的更多信息,請參見 new 修飾符(C# 參考)。
要將此行為與使用 override 的效果進行對比,請將以下方法添加到 DerivedClass。可在 public 的前面或后面添加 override 修飾符。
public override void Method1(){ Console.WriteLine("Derived - Method1");}
將 virtual 修飾符添加到 BaseClass 中的 Method1 的定義。可在 public 的前面或后面添加 virtual 修飾符。
public virtual void Method1(){ Console.WriteLine("Base - Method1");}
再次運行項目。尤其請注意下面輸出的最后兩行。
輸出:
Base - Method1Base - Method2Derived - Method1Derived - Method2Derived - Method1Base - Method2
使用 override 修飾符使 bcdc 能夠訪問 DerivedClass 中定義的 Method1 方法。通常,這是繼承層次結構中所需的行為。讓具有從派生類創建的值的對象使用派生類中定義的方法。通過使用 override 擴展基類方法可實現該行為。
下面的代碼包括完整的示例。
using System;using System.Text;namespace OverrideAndNew{ class Program { static void Main(string[] args) { BaseClass bc = new BaseClass(); DerivedClass dc = new DerivedClass(); BaseClass bcdc = new DerivedClass(); // The following two calls do what you would expect. They call // the methods that are defined in BaseClass. bc.Method1(); bc.Method2(); // Output: // Base - Method1 // Base - Method2 // The following two calls do what you would expect. They call // the methods that are defined in DerivedClass. dc.Method1(); dc.Method2(); // Output: // Derived - Method1 // Derived - Method2 // The following two calls produce different results, depending // on whether override (Method1) or new (Method2) is used. bcdc.Method1(); bcdc.Method2(); // Output: // Derived - Method1 // Base - Method2 } } class BaseClass { public virtual void Method1() { Console.WriteLine("Base - Method1"); } public virtual void Method2() { Console.WriteLine("Base - Method2"); } } class DerivedClass : BaseClass { public override void Method1() { Console.WriteLine("Derived - Method1"); } public new void Method2() { Console.WriteLine("Derived - Method2"); } }}
以下示例顯示了不同上下文中的類似行為。該示例定義了三個類:一個名為 Car 的基類,和兩個由其派生的 ConvertibleCar 和 Minivan?;愔邪?DescribeCar 方法。該方法給出了對一輛車的基本描述,然后調用 ShowDetails 來提供其他的信息。這三個類中的每一個類都定義了 ShowDetails 方法。 new 修飾符用于定義 ConvertibleCar 類中的 ShowDetails。 override 修飾符用于定義 Minivan 類中的 ShowDetails。
// Define the base class, Car. The class defines two methods,// DescribeCar and ShowDetails. DescribeCar calls ShowDetails, and each derived// class also defines a ShowDetails method. The example tests which version of// ShowDetails is selected, the base class method or the derived class method.class Car{ public void DescribeCar() { System.Console.WriteLine("Four wheels and an engine."); ShowDetails(); } public virtual void ShowDetails() { System.Console.WriteLine("Standard transportation."); }}// Define the derived classes.// Class ConvertibleCar uses the new modifier to acknowledge that ShowDetails// hides the base class method.class ConvertibleCar : Car{ public new void ShowDetails() { System.Console.WriteLine("A roof that opens up."); }}// Class Minivan uses the override modifier to specify that ShowDetails// extends the base class method.class Minivan : Car{ public override void ShowDetails() { System.Console.WriteLine("Carries seven people."); }}
該示例測試被調用的 ShowDetails 版本。以下方法,TestCars1 為每個類提供了一個實例,并在每個實例上調用 DescribeCar。
public static void TestCars1(){ System.Console.WriteLine("/nTestCars1"); System.Console.WriteLine("----------"); Car car1 = new Car(); car1.DescribeCar(); System.Console.WriteLine("----------"); // Notice the output from this test case. The new modifier is // used in the definition of ShowDetails in the ConvertibleCar // class. ConvertibleCar car2 = new ConvertibleCar(); car2.DescribeCar(); System.Console.WriteLine("----------"); Minivan car3 = new Minivan(); car3.DescribeCar(); System.Console.WriteLine("----------");}
TestCars1 生成以下輸出:尤其請注意 car2 的結果,該結果可能不是您所需的內容。對象的類型是 ConvertibleCar,但 DescribeCar 不會訪問 ConvertibleCar 中定義的 ShowDetails 版本,因為方法已聲明包含 new 修飾符,而不是 override 修飾符。因此,ConvertibleCar 對象顯示與 Car 對象相同的說明。比較 car3 的結果,它是一個 Minivan 對象。在這種情況下,在 Minivan 類中聲明的 ShowDetails 方法重寫 Car 類中聲明的 ShowDetails 方法,顯示的說明描述微型面包車。
// TestCars1// ----------// Four wheels and an engine.// Standard transportation.// ----------// Four wheels and an engine.// Standard transportation.// ----------// Four wheels and an engine.// Carries seven people.// ----------
TestCars2 創建 Car 類型的對象列表。對象的值由 Car、ConvertibleCar 和 Minivan 類實例化而來。 DescribeCar 是調用列表中的每個元素。以下代碼顯示了 TestCars2 的定義。
public static void TestCars2(){ System.Console.WriteLine("/nTestCars2"); System.Console.WriteLine("----------"); var cars = new List<Car> { new Car(), new ConvertibleCar(), new Minivan() }; foreach (var car in cars) { car.DescribeCar(); System.Console.WriteLine("----------"); }}
顯示以下輸出。請注意,此輸出與由 TestCars1 顯示的輸出相同。 ConvertibleCar 類的 ShowDetails 方法不被調用,無論對象的類型是 ConvertibleCar,如在 TestCars1 中,還是 Car,如在 TestCars2 中。相反,car3 在兩種情況下都從 Minivan 類調用 ShowDetails 方法,無論它具有類型 Minivan 還是類型 Car。
// TestCars2// ----------// Four wheels and an engine.// Standard transportation.// ----------// Four wheels and an engine.// Standard transportation.// ----------// Four wheels and an engine.// Carries seven people.// ----------
完成示例的方法 TestCars3 和 TestCars4。這些方法直接調用 ShowDetails,首先從宣布具有類型 ConvertibleCar 和 Minivan (TestCars3) 的對象調用,然后從具有類型 Car (TestCars4) 的對象調用。以下代碼定義了這兩種方法。
public static void TestCars3(){ System.Console.WriteLine("/nTestCars3"); System.Console.WriteLine("----------"); ConvertibleCar car2 = new ConvertibleCar(); Minivan car3 = new Minivan(); car2.ShowDetails(); car3.ShowDetails();}public static void TestCars4(){ System.Console.WriteLine("/nTestCars4"); System.Console.WriteLine("----------"); Car car2 = new ConvertibleCar(); Car car3 = new Minivan(); car2.ShowDetails(); car3.ShowDetails();}
該方法產生下面的輸出,它對應本主題中第一個示例的結果。
// TestCars3// ----------// A roof that opens up.// Carries seven people.// TestCars4// ----------// Standard transportation.// Carries seven people.
以下代碼顯示了整個項目及其輸出。
using System;using System.Collections.Generic;using System.Linq;using System.Text;namespace OverrideAndNew2{ class Program { static void Main(string[] args) { // Declare objects of the derived classes and test which version // of ShowDetails is run, base or derived. TestCars1(); // Declare objects of the base class, instantiated with the // derived classes, and repeat the tests. TestCars2(); // Declare objects of the derived classes and call ShowDetails // directly. TestCars3(); // Declare objects of the base class, instantiated with the // derived classes, and repeat the tests. TestCars4(); } public static void TestCars1() { System.Console.WriteLine("/nTestCars1"); System.Console.WriteLine("----------"); Car car1 = new Car(); car1.DescribeCar(); System.Console.WriteLine("----------"); // Notice the output from this test case. The new modifier is // used in the definition of ShowDetails in the ConvertibleCar // class. ConvertibleCar car2 = new ConvertibleCar(); car2.DescribeCar(); System.Console.WriteLine("----------"); Minivan car3 = new Minivan(); car3.DescribeCar(); System.Console.WriteLine("----------"); }
輸出:
TestCars1----------Four wheels and an engine.Standard transportation.----------Four wheels and an engine.Standard transportation.----------Four wheels and an engine.Carries seven people.----------
public static void TestCars2() { System.Console.WriteLine("/nTestCars2"); System.Console.WriteLine("----------"); var cars = new List<Car> { new Car(), new ConvertibleCar(), new Minivan() }; foreach (var car in cars) { car.DescribeCar(); System.Console.WriteLine("----------"); } }
輸出:
TestCars2----------Four wheels and an engine.Standard transportation.----------Four wheels and an engine.Standard transportation.----------Four wheels and an engine.Carries seven people.----------
public static void TestCars3() { System.Console.WriteLine("/nTestCars3"); System.Console.WriteLine("----------"); ConvertibleCar car2 = new ConvertibleCar(); Minivan car3 = new Minivan(); car2.ShowDetails(); car3.ShowDetails(); }
輸出:
TestCars3----------A roof that opens up.Carries seven people.
public static void TestCars4() { System.Console.WriteLine("/nTestCars4"); System.Console.WriteLine("----------"); Car car2 = new ConvertibleCar(); Car car3 = new Minivan(); car2.ShowDetails(); car3.ShowDetails(); } // Output: // TestCars4 // ---------- // Standard transportation. // Carries seven people. } // Define the base class, Car. The class defines two virtual methods, // DescribeCar and ShowDetails. DescribeCar calls ShowDetails, and each derived // class also defines a ShowDetails method. The example tests which version of // ShowDetails is used, the base class method or the derived class method. class Car { public virtual void DescribeCar() { System.Console.WriteLine("Four wheels and an engine."); ShowDetails(); } public virtual void ShowDetails() { System.Console.WriteLine("Standard transportation."); } } // Define the derived classes. // Class ConvertibleCar uses the new modifier to acknowledge that ShowDetails // hides the base class method. class ConvertibleCar : Car { public new void ShowDetails() { System.Console.WriteLine("A roof that opens up."); } } // Class Minivan uses the override modifier to specify that ShowDetails // extends the base class method. class Minivan : Car { public override void ShowDetails() { System.Console.WriteLine("Carries seven people."); } }}
新聞熱點
疑難解答