緣起
前一陣子, 有位朋友問到, C# 中繼承及多型中的 override 與 new 這 2 個 method 修飾字的使用方式及使用時機; 當時查了一下 MSDN 及一些 blog, 但仍不是很清楚; 最近終於有時間重新再 次 Study.關於 override 這個修飾字, 我想大部份的人都沒有太大問題.
關於 new 這個修飾字, 大概就感到很模糊. 一般文章會提到 遮蔽 (hide), 但到底遮蔽了什麼? 看了很久, 還是不清楚. 以筆者的想法, 其實就是 "遮蔽" 了 基底類別 呼叫 衍生類別方法的可能性.
以下文章的範例程式, 來自 MSDN.
名詞定義
一開始, 還是先定義一下名詞, 以免後續看程式碼的認知會有誤差.
1. 名詞定義:
左方 == 變數型別
右方 == 實體型別
2. 共有4種搭配, 但只有3種敘述符合語法.
(1) BaseClass bc = new BaseClass(); // 左方 == 右方 --> 表面非多型, 若實際呼叫 BaseClass 方法, 仍可能有多型
(2) DerivedClass dc = new DerivedClass(); // 左方 == 右方 --> 表面非多型, 若實際呼叫 BaseClass 方法, 仍可能有多型
(3) BaseClass bcdc = new DerivedClass(); // 左方 != 右方 --> 多型
// 以下敘述, 編譯時會出現以下錯誤 ...
// CS0266 Cannot implicitly convert type 'BaseClass' to 'DerivedClass'. An explicit conversion exists (are you missing a cast?)
(4) DerivedClass dcbc = new BaseClass();
3. new 修飾字是說: 該 method 在衍生類別裡是 "新" 的方法, 但會與基底類別並存; 如果由左方發動呼叫, 請不要呼叫該衍生類別的 method; 請直接呼叫左方 基底類別(變數型別) 的方法.
使用情境: 通常用在原本衍生類別有實作一個方法 (ex: Show()), 但後來基底類別也有實作同名同同參數的方法 (ex: Show()) /* 此時若不加衍生類別的方法如果不加 new 修飾字, 會出現 CS0108 的警告 */, 為確保衍生類別及基底類別各自的需求, 不希望基底類別去呼叫衍生類別的方法, 而造成錯誤. 故有 new 修飾字.
一般文章會提到 遮蔽 (hide), 但到底遮蔽了什麼? 看了很久, 很模糊. 以筆者的想法, 其實就是 "遮蔽" 了基底類別 呼叫 衍生類別方法的可能性.
這個有點像 "父子登山, 各有一片天".
這個有點像 "父子登山, 各有一片天".
4. override 修飾字是說: 該 method 在衍生類別裡是的 "霸道" 方法, 且覆寫基底類別的同名同參數的方法; 如果由左方發動呼叫, 請呼叫右方 衍生類別(實體型別) 的方法.
這個有點像 "立身行道, 揚名於後世, 以顯父母".
這個有點像 "立身行道, 揚名於後世, 以顯父母".
範例程式 (源碼)
基底類別及衍生類別
// 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("Base: Four wheels and an engine."); ShowDetails(); } public virtual void ShowDetails() { System.Console.WriteLine("Base: 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("Derived(new): 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("Derived(override): Carries seven people."); } }
測試函式
// 變數型別(左) 與實體型別(右) 相同, 且呼叫基底的 DescribeCar() 方法 public static void TestCars1() { System.Console.WriteLine("\nTestCars1"); System.Console.WriteLine("----------"); // car1 型別為 Car, 呼叫 Car 的 DescribeCar() 方法 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. // // The type of the object is ConvertibleCar, but DescribeCar does not access the version of ShowDetails that is defined in the ConvertibleCar class // because that method is declared with the new modifier, not the override modifier // // car2 型別為 ConvertibleCar // (1) 因為沒有 DescribeCar() 方法, 所以叫用基底類別 Car // (2) 有 ShowDetails() 方法, 但因為用 new 修飾, 所以叫用基底類別 Car // **重要** 也就是說 ConvertibleCar 的 ShowDetails() 方法, 被基底類別 Car 的 ShowDetails() 方法所 遮蔽 // **重要** 也就是說 ConvertibleCar 的 ShowDetails() 方法, 遮蔽了基底類別 Car 的 ShowDetails() 方法 ?? ConvertibleCar car2 = new ConvertibleCar(); car2.DescribeCar(); System.Console.WriteLine("----------"); // car3 型別為 Minivan // (1) 因為沒有 DescribeCar() 方法, 所以叫用基底類別 Car // (2) 有 ShowDetails() 方法, 但因為用 override 修飾, 所以叫用衍生類別 Minivan // **重要** 也就是說 Minivan 的 ShowDetails() 方法, 覆寫了 基底類別 Car 的 ShowDetails() 方法 Minivan car3 = new Minivan(); car3.DescribeCar(); System.Console.WriteLine("----------"); // // 沒有這種的寫法 ^^ // // CS0266 Cannot implicitly convert type 'UserQuery.Car' to 'UserQuery.Minivan'. An explicit conversion exists (are you missing a cast?) // Minivan car4 = new Car(); // car4.DescribeCar(); // System.Console.WriteLine("----------"); /* Output: TestCars1 ---------- Base: Four wheels and an engine. Base: Standard transportation. ---------- Base: Four wheels and an engine. Base: Standard transportation. ---------- Base: Four wheels and an engine. Derived(override): Carries seven people. ---------- */ }
// 加入多型的處理: // 變數型別(左) 與實體型別(右) 不同 --> 變數型別為 基底類別, 且呼叫基底的 DescribeCar() 方法 public static void TestCars2() { System.Console.WriteLine("\nTestCars2"); System.Console.WriteLine("----------"); // Car car1 = new Car(); // Car car2 = new ConvertibleCar(); // Car car3 = new Minivan(); // // car1 型別為 Car, 且實體為 Car, 所以呼叫到 ShowDetails() 時, 當然沒有問題, 一定是呼叫 Car 的 // car2 型別為 Car, 但實體為 ConvertibleCar, 所以呼叫到 ShowDetails() 時, 2 者都作檢查. 而本例 ConvertibleCar.ShowDetails() 為 new 修飾字, 故執行 Car 的 // car3 型別為 Car, 但實體為 Minivan, 所以呼叫到 ShowDetails() 時, 2 者都作檢查. 而本例 Minivan.ShowDetails() 為 override 修飾字, 故執行 Minivan 的 // var cars = new List{ new Car(), new ConvertibleCar(), new Minivan() }; foreach (var car in cars) { car.DescribeCar(); System.Console.WriteLine("----------"); } /* Output: TestCars2 ---------- Base: Four wheels and an engine. Base: Standard transportation. ---------- Base: Four wheels and an engine. Base: Standard transportation. ---------- Base: Four wheels and an engine. Derived(override): Carries seven people. ---------- */
// 變數型別(左) 與實體型別(右) 相同, 但沒有呼叫基底的方法 public static void TestCars3() { System.Console.WriteLine("\nTestCars3"); System.Console.WriteLine("----------"); ConvertibleCar car2 = new ConvertibleCar(); Minivan car3 = new Minivan(); car2.ShowDetails(); // car2 變數型別與實體型別均為 ConvertibleCar, 且本身有 ShowDetails() 方法, 當然用自己的 car3.ShowDetails(); // car3 變數型別為實體型別均為 Minivan, 且本身有 ShowDetails() 方法, 當然用自己的 /* Output: TestCars3 ---------- Derived(new): A roof that opens up. Derived(override): Carries seven people. */ }
// 變數型別(左) 與實體型別(右) 不同, 但沒有呼叫基底的方法 public static void TestCars4() { System.Console.WriteLine("\nTestCars4"); System.Console.WriteLine("----------"); Car car2 = new ConvertibleCar(); Car car3 = new Minivan(); car2.ShowDetails(); // car2 變數型別為 Car, 實體型別為 ConvertibleCar, 所以會作檢查; 因為 new 修飾, 所以呼叫 Car 的 car3.ShowDetails(); // car3 變數型別為 Car, 實體型別為 Minivan, 所以會作檢查; 因為 override 修飾, 所以呼叫 Minivan 的 /* Output: TestCars4 ---------- Base: Standard transportation. Derived(override): Carries seven people. */ }
主程式
void Main() { // Declare objects of the derived classes and test which version // of ShowDetails is run, base or derived. // 變數型別(左) 與實體型別(右) 相同, 且呼叫基底的 DescribeCar() 方法 TestCars1(); // Declare objects of the base class, instantiated with the // derived classes, and repeat the tests. // 變數型別(左) 與實體型別(右) 不同 --> 變數型別為 基底類別, 且呼叫基底的 DescribeCar() 方法 ==> 多型的效果 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(); }
範例程式 (流程解析)
茲將上述範例的流程解析如下圖, 以增加對於範例的理解.
總結
終於把 override 與 new 作了一些 Study, 留下記錄, 以供日後忘記時, 可作參考.參考文件
- 了解使用 Override 和 New 關鍵字的時機 (C# 程式設計手冊)
- 使用 Override 和 New 關鍵字進行版本控制 (C# 程式設計手冊)
- C# - new和override的差異與目的
.
沒有留言:
張貼留言