多型 (C# 程式設計手冊)
多型 (Polymorphism) 常被視為物件導向程式設計在封裝 (Encapsulation) 和繼承 (Inheritance) 以外的第三個支柱。 「多型」是一個希臘字,表示「多種形體」,並有兩個特殊的面向:
在執行階段,在如方法參數和集合或陣列等地方,衍生類別的物件會被視為基底類別的物件。 當這樣的情況發生時,物件的宣告型別和其在執行階段的型別將不再相同。
基底類別可以定義與實作虛擬「方法」(Method),而衍生類別可以覆寫它們,表示衍生類別會提供本身的定義與實作。 在執行階段,當用戶端程式碼呼叫方法時,CLR 會查詢物件的執行階段型別,並叫用虛擬方法的覆寫。 因此在您的原始程式碼中,您可以呼叫基底類別上的方法,並讓衍生類別版本的方法執行。
虛擬方法能讓您以統一的方法使用相關物件的群組。 例如,假設您有一個繪圖應用程式,可以讓使用者在繪圖介面上建立各種類型的圖案。 在編譯時期,您並不知道使用者究竟會建立哪一種圖案。 然而,應用程式必須追蹤建立的所有圖案類型,並根據使用者的滑鼠動作更新圖案。 您可以使用多型,以兩個基本的步驟解決這個問題:
建立類別階層,其中每個特定的圖案類別都衍生自一般的基底類別。
透過對基底類別方法的單一呼叫,使用虛擬方法叫用任何衍生類別上的適當方法。
首先,建立稱為 Shape 的基底類別,以及像 Rectangle、Circle 和 Triangle 等衍生類別。 給予 Shape 類別稱為 Draw 的虛擬方法,並在每個衍生類別中覆寫此方法,以繪製該類別所代表的特定圖案。 建立 List<Shape> 物件,並在其中加入 Circle、Triangle 和 Rectangle。 使用 foreach 迴圈逐一查看清單,並呼叫清單中每個 Shape 物件上的 Draw 方法,以更新繪圖介面。 雖然清單中的每個物件都有 Shape 的宣告型別,但會受到叫用的是執行階段型別 (每個衍生類別中受覆寫版本的方法)。
public class Shape
{
// A few example members
public int X { get; private set; }
public int Y { get; private set; }
public int Height { get; set; }
public int Width { get; set; }
// Virtual method
public virtual void Draw()
{
Console.WriteLine("Performing base class drawing tasks");
}
}
class Circle : Shape
{
public override void Draw()
{
// Code to draw a circle...
Console.WriteLine("Drawing a circle");
base.Draw();
}
}
class Rectangle : Shape
{
public override void Draw()
{
// Code to draw a rectangle...
Console.WriteLine("Drawing a rectangle");
base.Draw();
}
}
class Triangle : Shape
{
public override void Draw()
{
// Code to draw a triangle...
Console.WriteLine("Drawing a triangle");
base.Draw();
}
}
class Program
{
static void Main(string[] args)
{
// Polymorphism at work #1: a Rectangle, Triangle and Circle
// can all be used whereever a Shape is expected. No cast is
// required because an implicit conversion exists from a derived
// class to its base class.
System.Collections.Generic.List<Shape> shapes = new System.Collections.Generic.List<Shape>();
shapes.Add(new Rectangle());
shapes.Add(new Triangle());
shapes.Add(new Circle());
// Polymorphism at work #2: the virtual method Draw is
// invoked on each of the derived classes, not the base class.
foreach (Shape s in shapes)
{
s.Draw();
}
// Keep the console open in debug mode.
Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
}
/* Output:
Drawing a rectangle
Performing base class drawing tasks
Drawing a triangle
Performing base class drawing tasks
Drawing a circle
Performing base class drawing tasks
*/
在 C# 中,每個型別都是多型,因為包括使用者定義型別在內的所有型別,都繼承自 Object。
多型概觀
虛擬成員
當衍生類別繼承自基底類別時,就會取得基底類別的所有方法、欄位、屬性和事件。 衍生類別的設計工具可以選擇是否要
覆寫基底類別中的虛擬成員。
繼承最接近的基底類別方法,而不加以覆寫。
為隱藏基底類別實作的成員,定義新的非虛擬實作。
只有當基底成員宣告為virtual 或 abstract 時,衍生類別才能覆寫基底類別成員。 衍生成員必須使用 override 關鍵字明確指出方法要加入虛擬引動中。 下列程式碼提供一個範例:
public class BaseClass
{
public virtual void DoWork() { }
public virtual int WorkProperty
{
get { return 0; }
}
}
public class DerivedClass : BaseClass
{
public override void DoWork() { }
public override int WorkProperty
{
get { return 0; }
}
}
欄位不能是虛擬的,只有方法、屬性、事件和索引子可以是虛擬的。 當衍生類別覆寫虛擬成員時,即使該類別的執行個體是當做基底類別的執行個體存取時,也會呼叫該成員。 下列程式碼提供一個範例:
DerivedClass B = new DerivedClass();
B.DoWork(); // Calls the new method.
BaseClass A = (BaseClass)B;
A.DoWork(); // Also calls the new method.
虛擬方法和屬性能讓衍生類別擴充基底類別,而不需要使用方法的基底類別實作。 如需詳細資訊,請參閱使用 Override 和 New 關鍵字進行版本控制 (C# 程式設計手冊)。 介面會提供另一個方法,以定義一個方法或一組方法,其實作會留給衍生類別。 如需詳細資訊,請參閱 介面 (C# 程式設計手冊)和在類別和介面之間選擇。
使用新成員隱藏基底類別成員
如果您希望衍生成員和基底類別中的某個成員有一樣的名稱,但不想要衍生成員加入虛擬引動中,可以使用 new 關鍵字。 new 關鍵字會放在所要取代之類別成員的傳回型別前。 下列程式碼提供一個範例:
public class BaseClass
{
public void DoWork() { WorkField++; }
public int WorkField;
public int WorkProperty
{
get { return 0; }
}
}
public class DerivedClass : BaseClass
{
public new void DoWork() { WorkField++; }
public new int WorkField;
public new int WorkProperty
{
get { return 0; }
}
}
您還是可以將衍生類別的執行個體轉型為基底類別的執行個體,透過用戶端程式碼存取隱藏的基底類別成員。 例如:
DerivedClass B = new DerivedClass();
B.DoWork(); // Calls the new method.
BaseClass A = (BaseClass)B;
A.DoWork(); // Calls the old method.
避免衍生類別覆寫虛擬成員
虛擬成員會永遠保持虛擬的狀態,無論在原本宣告虛擬成員的類別和虛擬成員間又宣告了多少類別。 如果類別 A 宣告了虛擬成員,而類別 B 衍生自 A,類別 C 又衍生自 B,則類別 C 會繼承該虛擬成員,而且無論類別 B 宣告覆寫該成員與否,都可以覆寫該成員。 下列程式碼提供一個範例:
public class A
{
public virtual void DoWork() { }
}
public class B : A
{
public override void DoWork() { }
}
衍生類別可以藉由將覆寫宣告為 sealed,阻止進行虛擬繼承。 這項作業需要在類別成員宣告中的 override 關鍵字前,放置 sealed 關鍵字。 下列程式碼提供一個範例:
public class C : B
{
public sealed override void DoWork() { }
}
在上一個範例中,DoWork 對於任何衍生自 C 的類別都不再是虛擬的。 對於 C 的執行個體,即使它們轉型為型別 B 或型別 A,它依然是虛擬的。 透過使用 new 關鍵字,可以用衍生類別取代密封的方法,如下列範例所示:
public class D : C
{
public new void DoWork() { }
}
在上面的範例中,如果使用型別 D 的變數在 D 上呼叫 DoWork,就會呼叫新的 DoWork。 如果使用型別 C、B 或 A 的變數來存取 D 的執行個體,DoWork 呼叫就會照著虛擬繼承的規則,將呼叫傳送至類別 C 上的 DoWork 實作。
從衍生類別存取基底類別虛擬成員
取代或覆寫方法或屬性的衍生類別,仍可使用 base 關鍵字來存取基底類別的方法或屬性。 下列程式碼提供一個範例:
public class Base
{
public virtual void DoWork() {/*...*/ }
}
public class Derived : Base
{
public override void DoWork()
{
//Perform Derived's work here
//...
// Call DoWork on base class
base.DoWork();
}
}
如需詳細資訊,請參閱 base。
注意事項 |
---|
虛擬成員最好能在其實作中使用 base 呼叫該成員的基底類別實作。 使用基底類別行為可讓衍生類別只需實作其特有的行為。 如果不呼叫基底類別實作,衍生類別與基底類別的行為相容與否,就要視衍生類別而定。 |