共用方式為


多型 (C# 程式設計手冊)

多型 (Polymorphism) 常被視為物件導向程式設計在封裝 (Encapsulation) 和繼承 (Inheritance) 以外的第三個支柱。 「多型」是一個希臘字,表示「多種形體」,並有兩個特殊的面向:

  1. 在執行階段,在如方法參數和集合或陣列等地方,衍生類別的物件會被視為基底類別的物件。 當這樣的情況發生時,物件的宣告型別和其在執行階段的型別將不再相同。

  2. 基底類別可以定義與實作虛擬「方法」(Method),而衍生類別可以覆寫它們,表示衍生類別會提供本身的定義與實作。 在執行階段,當用戶端程式碼呼叫方法時,CLR 會查詢物件的執行階段型別,並叫用虛擬方法的覆寫。 因此在您的原始程式碼中,您可以呼叫基底類別上的方法,並讓衍生類別版本的方法執行。

虛擬方法能讓您以統一的方法使用相關物件的群組。 例如,假設您有一個繪圖應用程式,可以讓使用者在繪圖介面上建立各種類型的圖案。 在編譯時期,您並不知道使用者究竟會建立哪一種圖案。 然而,應用程式必須追蹤建立的所有圖案類型,並根據使用者的滑鼠動作更新圖案。 您可以使用多型,以兩個基本的步驟解決這個問題:

  1. 建立類別階層,其中每個特定的圖案類別都衍生自一般的基底類別。

  2. 透過對基底類別方法的單一呼叫,使用虛擬方法叫用任何衍生類別上的適當方法。

首先,建立稱為 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

多型概觀

虛擬成員

當衍生類別繼承自基底類別時,就會取得基底類別的所有方法、欄位、屬性和事件。 衍生類別的設計工具可以選擇是否要

  • 覆寫基底類別中的虛擬成員。

  • 繼承最接近的基底類別方法,而不加以覆寫。

  • 為隱藏基底類別實作的成員,定義新的非虛擬實作。

只有當基底成員宣告為virtualabstract 時,衍生類別才能覆寫基底類別成員。 衍生成員必須使用 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 呼叫該成員的基底類別實作。 使用基底類別行為可讓衍生類別只需實作其特有的行為。 如果不呼叫基底類別實作,衍生類別與基底類別的行為相容與否,就要視衍生類別而定。

本章節內容

請參閱

參考

繼承 (C# 程式設計手冊)

抽象和密封類別以及類別成員 (C# 程式設計手冊)

方法 (C# 程式設計手冊)

事件 (C# 程式設計手冊)

屬性 (C# 程式設計手冊)

索引子 (C# 程式設計手冊)

型別 (C# 程式設計手冊)

概念

C# 程式設計手冊

C# 程式設計手冊