︿
Top

2015年12月10日 星期四

C#: override, new difference and scenario

緣起

前一陣子, 有位朋友問到, 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, 留下記錄, 以供日後忘記時, 可作參考.

參考文件



.

沒有留言:

張貼留言