共用方式為


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

多型通常是指物件導向程式設計的第三個重要部分,其重要性僅次於封裝和繼承。 多型在希臘文中表示「多種形狀」,可分成下列兩方面:

  • 在執行階段,衍生類別物件可視為方法參數和集合或陣列等位置中的基底類別物件。 發生此情況時,物件的宣告類型與其執行階段類型將不再相同。

  • 基底類別可定義及實作虛擬「方法」(Method),而衍生類別可覆寫這些方法,換句話說,衍生類別會提供自己的定義和實作。 在執行階段,當用戶端程式碼呼叫方法時,CLR 會查詢物件的執行階段類型,然後叫用虛擬方法的覆寫。 因此,在您的原始程式碼中,您可以在基底類別上呼叫方法,然後執行衍生類別版本的方法。

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

  1. 建立類別階層架構,其中每個特定圖形類別都是衍生自一個通用基底類別。

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

首先,建立稱為 Shape 的基底類別,以及 Rectangle、Circle 和 Triangle 等衍生類別。 將稱為 Draw 的虛擬方法提供給 Shape 類別,然後在每個衍生類別中覆寫此方法,以繪製該類別代表的特定圖形。 建立 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 是否宣告該成員的覆寫,類別 C 都可以選擇覆寫該成員。 下列程式碼提供一個範例:

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# 程式設計手冊