繼承 - 衍生類型以建立更特製化的行為
繼承是物件導向程式設計的三個主要特性之一,另外兩個是封裝和多型。 繼承可讓您建立新的類別,以重複使用、擴充和修改其他類別中定義的行為。 成員被繼承的類別稱為「基底類別」,而繼承這種成員的類別即稱為「衍生類別」。 衍生類別只能有一個基底類別。 不過,繼承是可轉移的。 如果 ClassC
衍生自 ClassB
,且 ClassB
衍生自 ClassA
, ClassC
則會繼承 和 ClassA
中 ClassB
宣告的成員。
注意
結構不支援繼承,但可以實作介面。
就概念而言,衍生類別是基底類別的特製化項目。 例如,如果您有一個基底類別 Animal
,您可能會有一個名為 Mammal
的衍生類別,以及另一個名為 Reptile
的衍生類別。 Mammal
是 Animal
,Reptile
也是 Animal
,但這兩個衍生類別各代表不同的基底類別特製化項目。
介面宣告可能會為其成員定義預設實作。 這些實作會由衍生介面繼承,以及實作這些介面的類別。 如需預設介面方法的詳細資訊,請參閱 介面的文章。
當您將某個類別定義為要從另一個類別衍生時,衍生類別會隱含取得基底類別的所有成員,但建構函式和完成項則除外。 衍生類別會重複使用基類中的程式碼,而不需要重新實作。 您可以在衍生類別中新增更多成員。 衍生類別會擴充基類的功能。
下圖顯示 WorkItem
類別,代表某些商務程序中的工作項目。 它和所有類別一樣,會衍生自 System.Object 並繼承其所有方法。 WorkItem
新增其本身的六個成員。 這些成員包含建構函式,因為不會繼承建構函式。 ChangeRequest
類別繼承自 WorkItem
,並代表特定類型的工作項目。 ChangeRequest
會在繼承自 WorkItem
和 Object 的成員中,另外新增兩個成員。 它必須新增自己的建構函式,也會新增 originalItemID
。 originalItemID
屬性可讓 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> 不表示基類與衍生 (類別之間存在的相同「是」關聯性,例如,是 Mammal
Animal
) 。 如需詳細資訊,請參閱介面。
防止進一步衍生
類別可以藉由將本身或成員宣告為 sealed
,以防止其他類別繼承自它或其任何成員。
基類成員的衍生類別隱藏
衍生類別可藉由以相同的名稱和簽章宣告基底類別成員,來隱藏這些成員。 new
修飾詞可用來明確指出成員不是基底成員的覆寫。 不需要使用 new
,但如果未使用,則會產生 new
編譯器警告。 如需詳細資訊,請參閱使用 Override 和 New 關鍵字進行版本控制和了解使用 Override 和 New 關鍵字的時機。