C#中Override關(guān)鍵字和New關(guān)鍵字的用法詳解
C# 語言經(jīng)過專門設(shè)計,以便不同庫中的基類與派生類之間的版本控制可以不斷向前發(fā)展,同時保持向后兼容。這具有多方面的意義。例如,這意味著在基類中引入與派生類中的某個成員具有相同名稱的新成員在 C# 中是完全支持的,不會導(dǎo)致意外行為。它還意味著類必須顯式聲明某方法是要重寫一個繼承方法,還是一個隱藏具有類似名稱的繼承方法的新方法。
在 C# 中,派生類可以包含與基類方法同名的方法。
基類方法必須定義為 virtual。
- 如果派生類中的方法前面沒有 new 或 override 關(guān)鍵字,則編譯器將發(fā)出警告,該方法將有如存在 new 關(guān)鍵字一樣執(zhí)行操作。
- 如果派生類中的方法前面帶有 new 關(guān)鍵字,則該方法被定義為獨(dú)立于基類中的方法。
- 如果派生類中的方法前面帶有 override 關(guān)鍵字,則派生類的對象將調(diào)用該方法,而不是調(diào)用基類方法。
可以從派生類中使用 base 關(guān)鍵字調(diào)用基類方法。
override、virtual 和 new 關(guān)鍵字還可以用于屬性、索引器和事件中。
默認(rèn)情況下,C# 方法為非虛方法。如果某個方法被聲明為虛方法,則繼承該方法的任何類都可以實現(xiàn)它自己的版本。若要使方法成為虛方法,必須在基類的方法聲明中使用 virtual 修飾符。然后,派生類可以使用 override 關(guān)鍵字重寫基虛方法,或使用 new 關(guān)鍵字隱藏基類中的虛方法。如果 override 關(guān)鍵字和 new 關(guān)鍵字均未指定,編譯器將發(fā)出警告,并且派生類中的方法將隱藏基類中的方法。
為了在實踐中演示上述情況,我們暫時假定公司 A 創(chuàng)建了一個名為 GraphicsClass 的類,您的程序?qū)⑹褂么祟悺?GraphicsClass 如下所示:
class GraphicsClass { public virtual void DrawLine() { } public virtual void DrawPoint() { } }
您的公司使用此類,并且您在添加新方法時將其用來派生自己的類:
class YourDerivedGraphicsClass : GraphicsClass { public void DrawRectangle() { } }
您的應(yīng)用程序運(yùn)行正常,直到公司 A 發(fā)布了 GraphicsClass 的新版本,類似于下面的代碼:
class GraphicsClass { public virtual void DrawLine() { } public virtual void DrawPoint() { } public virtual void DrawRectangle() { } }
現(xiàn)在,GraphicsClass 的新版本中包含一個名為 DrawRectangle 的方法。開始時,沒有出現(xiàn)任何問題。新版本仍然與舊版本保持二進(jìn)制兼容。已經(jīng)部署的任何軟件都將繼續(xù)正常工作,即使新類已安裝到這些軟件所在的計算機(jī)系統(tǒng)上。在您的派生類中,對方法 DrawRectangle 的任何現(xiàn)有調(diào)用將繼續(xù)引用您的版本。
但是,一旦您使用 GraphicsClass 的新版本重新編譯應(yīng)用程序,就會收到來自編譯器的警告 CS0108。此警告提示您必須考慮希望 DrawRectangle 方法在應(yīng)用程序中的工作方式。
如果您希望自己的方法重寫新的基類方法,請使用 override 關(guān)鍵字:
class YourDerivedGraphicsClass : GraphicsClass { public override void DrawRectangle() { } }
override 關(guān)鍵字可確保派生自 YourDerivedGraphicsClass 的任何對象都將使用 DrawRectangle 的派生類版本。派生自 YourDerivedGraphicsClass 的對象仍可以使用基關(guān)鍵字訪問 DrawRectangle 的基類版本:
base.DrawRectangle();
如果您不希望自己的方法重寫新的基類方法,則需要注意以下事項。為了避免這兩個方法之間發(fā)生混淆,可以重命名您的方法。這可能很耗費(fèi)時間且容易出錯,而且在某些情況下并不可行。但是,如果您的項目相對較小,則可以使用 Visual Studio 的重構(gòu)選項來重命名方法。
或者,也可以通過在派生類定義中使用關(guān)鍵字 new 來防止出現(xiàn)該警告:
class YourDerivedGraphicsClass : GraphicsClass { public new void DrawRectangle() { } }
使用 new 關(guān)鍵字可告訴編譯器您的定義將隱藏基類中包含的定義。這是默認(rèn)行為。
重寫和方法選擇
當(dāng)在類中指定方法時,如果有多個方法與調(diào)用兼容(例如,存在兩種同名的方法,并且其參數(shù)與傳遞的參數(shù)兼容),則 C# 編譯器將選擇最佳方法進(jìn)行調(diào)用。下面的方法將是兼容的:
public class Derived : Base { public override void DoWork(int param) { } public void DoWork(double param) { } }
在 Derived 的一個實例中調(diào)用 DoWork 時,C# 編譯器將首先嘗試使該調(diào)用與最初在 Derived 上聲明的 DoWork 版本兼容。重寫方法不被視為是在類上進(jìn)行聲明的,而是在基類上聲明的方法的新實現(xiàn)。僅當(dāng) C# 編譯器無法將方法調(diào)用與 Derived 上的原始方法匹配時,它才嘗試將該調(diào)用與具有相同名稱和兼容參數(shù)的重寫方法匹配。例如:
int val = 5; Derived d = new Derived(); d.DoWork(val); // Calls DoWork(double).
由于變量 val 可以隱式轉(zhuǎn)換為 double 類型,因此 C# 編譯器將調(diào)用 DoWork(double),而不是 DoWork(int)。有兩種方法可以避免此情況。首先,避免將新方法聲明為與虛方法同名。其次,可以通過將 Derived 的實例強(qiáng)制轉(zhuǎn)換為 Base 來使 C# 編譯器搜索基類方法列表,從而使其調(diào)用虛方法。由于是虛方法,因此將調(diào)用 Derived 上的 DoWork(int) 的實現(xiàn)。例如:
((Base)d).DoWork(val); // Calls DoWork(int) on Derived.
何時使用 Override 和 New 關(guān)鍵字
在 C# 中,派生類中方法的名稱可與基類中方法的名稱相同??赏ㄟ^使用 new 和 override 關(guān)鍵字指定方法互動的方式。 override 修飾符 extends 基類方法,且 new 修飾符將其“隱藏”起來。這種區(qū)別在本主題中的示例顯示出來。
在控制臺應(yīng)用程序中,聲明下面的 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 的類型為 BaseClass,并且其值的類型為 BaseClass。
- dc的類型為 DerivedClass,并且其值的類型為 DerivedClass。
- bcdc的類型為 BaseClass,并且其值的類型為 DerivedClass。這是要密切注意的變量。
由于 bc 和 bcdc 具有類型 BaseClass,因此,除非您使用強(qiáng)制轉(zhuǎn)換,否則它們只會直接訪問 Method1。變量 dc 可以訪問 Method1 和 Method2。下面的代碼演示這些關(guān)系。
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 現(xiàn)在有 Method2 方法,因此可以為 BaseClass 變量 bc 和 bcdc 添加第二個調(diào)用語句,如下面的代碼所示。
bc.Method1(); bc.Method2(); dc.Method1(); dc.Method2(); bcdc.Method1(); bcdc.Method2();
當(dāng)生成項目時,您將看到在 BaseClass 中添加 Method2 方法將引發(fā)警告。警告提示,DerivedClass 中的 Method2 方法將 Method2 方法隱藏在 BaseClass 中。如果要獲得該結(jié)果,則建議您使用 Method2 定義中的 new 關(guān)鍵字?;蛘?,可以重命名 Method2 方法之一來解決警告,但這始終不實用。
在添加 new 之前,運(yùn)行該程序以查看其他調(diào)用語句生成的輸出。顯示以下結(jié)果。
輸出:
Base - Method1 Base - Method2 Base - Method1 Derived - Method2 Base - Method1 Base - Method2
new 關(guān)鍵字可以保留生成輸出的關(guān)系,但它將取消警告。具有 BaseClass 類型的變量繼續(xù)訪問 BaseClass 成員,具有 DerivedClass 類型的變量首先繼續(xù)訪問 DerivedClass 中的成員,然后再考慮從 BaseClass 繼承的成員.
要禁止顯示警告,請向 DerivedClass 中的 Method2 定義添加 new 修飾符,如下面的示例所示:可在 public 前后添加修飾符。
public new void Method2() { Console.WriteLine("Derived - Method2"); }
再次運(yùn)行該程序以確認(rèn)沒有更改輸出。還確認(rèn)警告不再出現(xiàn)。通過使用 new,您斷言您了解它修改的成員將隱藏從基類繼承的成員。關(guān)于通過繼承隱藏名稱的更多信息,請參見 new 修飾符(C# 參考)。
要將此行為與使用 override 的效果進(jìn)行對比,請將以下方法添加到 DerivedClass??稍?public 的前面或后面添加 override 修飾符。
public override void Method1() { Console.WriteLine("Derived - Method1"); }
將 virtual 修飾符添加到 BaseClass 中的 Method1 的定義??稍?public 的前面或后面添加 virtual 修飾符。
public virtual void Method1() { Console.WriteLine("Base - Method1"); }
再次運(yùn)行項目。尤其請注意下面輸出的最后兩行。
輸出:
Base - Method1 Base - Method2 Derived - Method1 Derived - Method2 Derived - Method1 Base - Method2
使用 override 修飾符使 bcdc 能夠訪問 DerivedClass 中定義的 Method1 方法。通常,這是繼承層次結(jié)構(gòu)中所需的行為。讓具有從派生類創(chuàng)建的值的對象使用派生類中定義的方法。通過使用 override 擴(kuò)展基類方法可實現(xiàn)該行為。
下面的代碼包括完整的示例。
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 方法。該方法給出了對一輛車的基本描述,然后調(diào)用 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."); } }
該示例測試被調(diào)用的 ShowDetails 版本。以下方法,TestCars1 為每個類提供了一個實例,并在每個實例上調(diào)用 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 的結(jié)果,該結(jié)果可能不是您所需的內(nèi)容。對象的類型是 ConvertibleCar,但 DescribeCar 不會訪問 ConvertibleCar 中定義的 ShowDetails 版本,因為方法已聲明包含 new 修飾符,而不是 override 修飾符。因此,ConvertibleCar 對象顯示與 Car 對象相同的說明。比較 car3 的結(jié)果,它是一個 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 創(chuàng)建 Car 類型的對象列表。對象的值由 Car、ConvertibleCar 和 Minivan 類實例化而來。 DescribeCar 是調(diào)用列表中的每個元素。以下代碼顯示了 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 方法不被調(diào)用,無論對象的類型是 ConvertibleCar,如在 TestCars1 中,還是 Car,如在 TestCars2 中。相反,car3 在兩種情況下都從 Minivan 類調(diào)用 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。這些方法直接調(diào)用 ShowDetails,首先從宣布具有類型 ConvertibleCar 和 Minivan (TestCars3) 的對象調(diào)用,然后從具有類型 Car (TestCars4) 的對象調(diào)用。以下代碼定義了這兩種方法。
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(); }
該方法產(chǎn)生下面的輸出,它對應(yīng)本主題中第一個示例的結(jié)果。
// 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."); } } }
相關(guān)文章
c#中利用委托反射將DataTable轉(zhuǎn)換為實體集的代碼
c#中利用委托反射將DataTable轉(zhuǎn)換為實體集的代碼,需要的朋友可以參考下2012-10-10c# Newtonsoft.Json 常用方法總結(jié)
這篇文章主要介紹了c# Newtonsoft.Json 常用方法的相關(guān)資料,幫助大家更好的理解和學(xué)習(xí)使用c#,感興趣的朋友可以了解下2021-02-02C#中枚舉類型和radiobox關(guān)聯(lián)操作的方法
這篇文章主要介紹了C#中枚舉類型和radiobox關(guān)聯(lián)操作的方法,實例分析了C#中枚舉類型及與控件關(guān)聯(lián)操作的相關(guān)技巧,需要的朋友可以參考下2015-04-04小白2分鐘學(xué)會Visual Studio如何將引用包打包到NuGet上
這篇文章主要介紹了小白2分鐘學(xué)會Visual Studio如何將引用包打包到NuGet上,只需兩步完成打包上傳操作,需要的朋友可以參考下2021-09-09