分享方式:


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

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

注意

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

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

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

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

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

顯示類別繼承的圖表

下列範例示範如何以 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 關鍵字的時機