繼承 - 衍生類型以建立更特製化的行為
繼承是物件導向程式設計的三個主要特性之一,另外兩個是封裝和多型。 繼承可讓您建立新類別以重複使用、擴充和修改其他類別中所定義的行為。 成員被繼承的類別稱為「基底類別」,而繼承這種成員的類別即稱為「衍生類別」。 衍生類別只能有一個基底類別。 不過,繼承是可轉移的。 如果 ClassC
衍生自 ClassB
,且 ClassB
衍生自 ClassA
,則 ClassC
會繼承在 ClassB
和 ClassA
中所宣告的成員。
注意
結構不支援繼承,但可以實作介面。
就概念而言,衍生類別是基底類別的特製化項目。 例如,如果您有一個基底類別 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 關鍵字的時機。