繼承 - 衍生類型以建立更特製化的行為

繼承是物件導向程式設計的三個主要特性之一,另外兩個是封裝和多型。 繼承可讓您建立新的類別,以重複使用、擴充和修改其他類別中定義的行為。 成員被繼承的類別稱為「基底類別」,而繼承這種成員的類別即稱為「衍生類別」。 衍生類別只能有一個基底類別。 不過,繼承是可轉移的。 如果 ClassC 衍生自 ClassB ,且 ClassB 衍生自 ClassAClassC 則會繼承 和 ClassAClassB 宣告的成員。

注意

結構不支援繼承,但可以實作介面。

就概念而言,衍生類別是基底類別的特製化項目。 例如,如果您有一個基底類別 Animal,您可能會有一個名為 Mammal 的衍生類別,以及另一個名為 Reptile 的衍生類別。 MammalAnimalReptile 也是 Animal,但這兩個衍生類別各代表不同的基底類別特製化項目。

介面宣告可能會為其成員定義預設實作。 這些實作會由衍生介面繼承,以及實作這些介面的類別。 如需預設介面方法的詳細資訊,請參閱 介面的文章。

當您將某個類別定義為要從另一個類別衍生時,衍生類別會隱含取得基底類別的所有成員,但建構函式和完成項則除外。 衍生類別會重複使用基類中的程式碼,而不需要重新實作。 您可以在衍生類別中新增更多成員。 衍生類別會擴充基類的功能。

下圖顯示 WorkItem 類別,代表某些商務程序中的工作項目。 它和所有類別一樣,會衍生自 System.Object 並繼承其所有方法。 WorkItem 新增其本身的六個成員。 這些成員包含建構函式,因為不會繼承建構函式。 ChangeRequest 類別繼承自 WorkItem,並代表特定類型的工作項目。 ChangeRequest 會在繼承自 WorkItemObject 的成員中,另外新增兩個成員。 它必須新增自己的建構函式,也會新增 originalItemIDoriginalItemID 屬性可讓 ChangeRequest 執行個體與套用變更要求的原始 WorkItem 產生關聯。

Diagram that shows class inheritance

下列範例示範如何以 C# 表示上圖所示範的類別關聯性。 此範例也會示範 WorkItem 如何覆寫 Object.ToString 虛擬方法,以及 ChangeRequest 類別如何繼承方法的 WorkItem 實作。 第一個區塊會定義類別:

// WorkItem implicitly inherits from the Object class.
public class WorkItem
{
    // Static field currentID stores the job ID of the last WorkItem that
    // has been created.
    private static int currentID;

    //Properties.
    protected int ID { get; set; }
    protected string Title { get; set; }
    protected string Description { get; set; }
    protected TimeSpan jobLength { get; set; }

    // Default constructor. If a derived class does not invoke a base-
    // class constructor explicitly, the default constructor is called
    // implicitly.
    public WorkItem()
    {
        ID = 0;
        Title = "Default title";
        Description = "Default description.";
        jobLength = new TimeSpan();
    }

    // Instance constructor that has three parameters.
    public WorkItem(string title, string desc, TimeSpan joblen)
    {
        this.ID = GetNextID();
        this.Title = title;
        this.Description = desc;
        this.jobLength = joblen;
    }

    // Static constructor to initialize the static member, currentID. This
    // constructor is called one time, automatically, before any instance
    // of WorkItem or ChangeRequest is created, or currentID is referenced.
    static WorkItem() => currentID = 0;

    // currentID is a static field. It is incremented each time a new
    // instance of WorkItem is created.
    protected int GetNextID() => ++currentID;

    // Method Update enables you to update the title and job length of an
    // existing WorkItem object.
    public void Update(string title, TimeSpan joblen)
    {
        this.Title = title;
        this.jobLength = joblen;
    }

    // Virtual method override of the ToString method that is inherited
    // from System.Object.
    public override string ToString() =>
        $"{this.ID} - {this.Title}";
}

// ChangeRequest derives from WorkItem and adds a property (originalItemID)
// and two constructors.
public class ChangeRequest : WorkItem
{
    protected int originalItemID { get; set; }

    // Constructors. Because neither constructor calls a base-class
    // constructor explicitly, the default constructor in the base class
    // is called implicitly. The base class must contain a default
    // constructor.

    // Default constructor for the derived class.
    public ChangeRequest() { }

    // Instance constructor that has four parameters.
    public ChangeRequest(string title, string desc, TimeSpan jobLen,
                         int originalID)
    {
        // The following properties and the GetNexID method are inherited
        // from WorkItem.
        this.ID = GetNextID();
        this.Title = title;
        this.Description = desc;
        this.jobLength = jobLen;

        // Property originalItemId is a member of ChangeRequest, but not
        // of WorkItem.
        this.originalItemID = originalID;
    }
}

下一個區塊示範如何使用基底和衍生類別:

// Create an instance of WorkItem by using the constructor in the
// base class that takes three arguments.
WorkItem item = new WorkItem("Fix Bugs",
                            "Fix all bugs in my code branch",
                            new TimeSpan(3, 4, 0, 0));

// Create an instance of ChangeRequest by using the constructor in
// the derived class that takes four arguments.
ChangeRequest change = new ChangeRequest("Change Base Class Design",
                                        "Add members to the class",
                                        new TimeSpan(4, 0, 0),
                                        1);

// Use the ToString method defined in WorkItem.
Console.WriteLine(item.ToString());

// Use the inherited Update method to change the title of the
// ChangeRequest object.
change.Update("Change the Design of the Base Class",
    new TimeSpan(4, 0, 0));

// ChangeRequest inherits WorkItem's override of ToString.
Console.WriteLine(change.ToString());
/* Output:
    1 - Fix Bugs
    2 - Change the Design of the Base Class
*/

抽象和虛擬方法

當基類將方法宣告為 virtual 時,衍生類別可以使用 override 自己的實作來建立方法。 如果基類將成員宣告為 abstract ,則必須在任何直接繼承自該類別的非抽象類別中覆寫該方法。 如果衍生類別本身就是抽象的,則會繼承抽象成員而不需要進行實作。 抽象和虛擬成員是多型的基礎,而多型是物件導向程式設計的第二個主要特性。 如需詳細資訊,請參閱多型

抽象基類

如果您想要防止使用 new 運算子來直接具現化,您可以將類別宣告為抽象。 只有當新的類別衍生自抽象類別時,才能使用抽象類別。 抽象類別可以包含一或多個本身宣告為抽象的方法簽章。 這些簽章可指定參數和傳回值,但沒有實作 (方法主體)。 抽象類別不需要包含抽象成員;不過,如果類別確實包含抽象成員,則必須將類別本身宣告為抽象。 不是抽象本身的衍生類別必須提供抽象基類中任何抽象方法的實作。

介面

介面是定義一組成員的參考型別。 所有實作該介面的類別和結構都必須實作該組成員。 介面可能會為上述任何或所有成員定義預設實作。 一個類別可以實作多個介面,但只能衍生自單一直接基底類別。

介面可用來定義不一定具有「是」關聯性的類別的特定功能。 例如,介面可由任何類別或結構實作, System.IEquatable<T> 以判斷類型的兩個物件是否相等 (不過類型定義等價) 。 IEquatable<T> 不表示基類與衍生 (類別之間存在的相同「是」關聯性,例如,是 MammalAnimal) 。 如需詳細資訊,請參閱介面

防止進一步衍生

類別可以藉由將本身或成員宣告為 sealed ,以防止其他類別繼承自它或其任何成員。

基類成員的衍生類別隱藏

衍生類別可藉由以相同的名稱和簽章宣告基底類別成員,來隱藏這些成員。 new修飾詞可用來明確指出成員不是基底成員的覆寫。 不需要使用 new ,但如果未使用,則會產生 new 編譯器警告。 如需詳細資訊,請參閱使用 Override 和 New 關鍵字進行版本控制了解使用 Override 和 New 關鍵字的時機