継承 (C# プログラミング ガイド)
継承は、カプセル化やポリモーフィズムと共に、オブジェクト指向プログラミングの重要な 3 つの特性 (柱) の 1 つです。継承を使用すると、他のクラスで定義されている動作を再利用、拡張、および変更する新しいクラスを作成できます。メンバーが継承される側のクラスを基本クラスと呼び、メンバーを継承する側のクラスを派生クラスと呼びます。派生クラスは直接基本クラスを 1 つだけ持つことができます。ただし、継承には推移性があります。ClassC が ClassB から派生し、ClassB が ClassA から派生している場合、ClassC には ClassB と ClassA で宣言されたメンバーが継承されます。
[!メモ]
構造体では、継承はサポートされませんが、インターフェイスを実装することはできます。詳細については、「インターフェイス (C# プログラミング ガイド)」を参照してください。
概念的には、派生クラスは基本クラスから特化したクラスです。たとえば、基本クラス Animal がある場合、Mammal という名前の派生クラスと、Reptile という名前の別の派生クラスを作成できます。Mammal は Animal の 1 つであり、Reptile も Animal の 1 つですが、それぞれの派生クラスは、基本クラスからの別々の特化を表します。
別のクラスから派生するクラスを定義するとき、派生クラスには、基本クラスのコンストラクターとデストラクターを除くすべてのメンバーが暗黙的に引き継がれます。したがって、派生クラスでは、基本クラスのコードを再実装しなくても再利用できます。派生クラスにメンバーを追加することもできます。このような方法で、派生クラスは基本クラスの機能を拡張します。
次の図は、あるビジネス プロセスにおける作業項目を表す WorkItem クラスを示します。すべてのクラスと同様に、このクラスは System.Object から派生し、そのすべてのメソッドを継承します。WorkItem には、独自のメンバーが 5 つ追加されています。これにはコンストラクターが含まれます。コンストラクターは継承されないためです。WorkItem から継承される ChangeRequest クラスは、特定の種類の作業項目を表します。ChangeRequest には、WorkItem および Object から継承されるメンバーに、さらに 2 つのメンバーが追加されています。追加される必要がある独自のコンストラクターの他に、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;
}
protected int GetNextID()
{
// currentID is a static field. It is incremented each time a new
// instance of WorkItem is created.
return ++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()
{
return String.Format("{0} - {1}", 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;
}
}
class Program
{
static void Main()
{
// 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());
// Keep the console open in debug mode.
Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
}
/* Output:
1 - Fix Bugs
2 - Change the Design of the Base Class
*/
抽象メソッドと仮想メソッド
基本クラスでメソッドが virtual として宣言されている場合、派生クラスはそのメソッドを独自の実装でオーバーライドできます。基本クラスでメンバーが abstract として宣言されている場合は、クラスから直接継承される非抽象クラスで、そのメソッドをオーバーライドする必要があります。派生クラス自体が抽象クラスの場合は、抽象メンバーを実装せずに継承します。抽象メンバーと仮想メンバーは、オブジェクト指向プログラミングの重要な特性の 2 つ目であるポリモーフィズムの基礎です。詳細については、「ポリモーフィズム (C# プログラミング ガイド)」を参照してください。
抽象基本クラス
new キーワードを使用した直接のインスタンス化を禁止する場合は、クラスを abstract として宣言できます。このようにすると、そのクラスは、新しいクラスを派生させなければ使用できなくなります。抽象クラスには、それ自身が abstract として宣言された 1 つ以上のメソッド シグネチャを含むことができます。これらのシグネチャはパラメーターと戻り値を指定しますが、実装 (メソッド本体) は持ちません。抽象クラスは必ずしも抽象メンバーを含む必要はありませんが、クラスに抽象メンバーが含まれている場合は、クラス自体を抽象クラスとして宣言する必要があります。それ自身が抽象クラスでない派生クラスは、抽象基本クラスから継承した抽象メソッドをすべて実装する必要があります。詳細については、「抽象クラスとシール クラス、およびクラス メンバー (C# プログラミング ガイド)」を参照してください。
インターフェイス
インターフェイスは、抽象メンバーだけで構成される抽象基本クラスに似た参照型です。クラスでインターフェイスを実装する場合は、そのインターフェイスのすべてのメンバーを実装する必要があります。1 つのクラスで複数のインターフェイスを実装できますが、直接継承できる基本クラスは 1 つだけです。
インターフェイスは、必ずしも "is-a" 関係を持たない、クラスの特定の機能を定義するために使用します。たとえば、ある型の 2 つのオブジェクトが等しいかどうかをクライアント コードで判別できるようにする (ただし、等価性の定義は型によって行う) 場合は、そのクラスまたは構造体で System.IEquatable<T> インターフェイスを実装できます。IEquatable<T> は、基本クラスと派生クラスの間に存在する is-a 関係 ("Mammal は Animal である" など) と同様の意味を示すものではありません。詳細については、「インターフェイス (C# プログラミング ガイド)」を参照してください。
派生クラスによる基本クラスのメンバーへのアクセス
派生クラスは、基本クラスのパブリック メンバー、プロテクト メンバー、内部メンバー、およびプロテクト内部メンバーにアクセスできます。派生クラスは基本クラスのプライベート メンバーを継承しますが、これらのメンバーにはアクセスできません。ただし、これらのすべてのプライベート メンバーは派生クラスにも存在し、基本クラス自体で行われるのと同じ処理を実行できます。たとえば、基本クラスのプロテクト メソッドがプライベート フィールドにアクセスするとします。継承された基本クラスのメソッドが正しく動作するためには、派生クラスにそのフィールドが存在する必要があります。
それ以上の派生の禁止
クラス自身またはメンバーを sealed として宣言することで、他のクラスがそのクラスやメンバーを継承できないように指定できます詳細については、「抽象クラスとシール クラス、およびクラス メンバー (C# プログラミング ガイド)」を参照してください。
派生クラスによる基本クラスのメンバーの隠ぺい
派生クラスでは、同じ名前とシグネチャでメンバーを宣言することで、基本クラスのメンバーを隠ぺいできます。new 修飾子を使用すると、そのメンバーが基本メンバーのオーバーライドとして用意されているのではないことを明示的に指定できます。new の使用は必須ではありませんが、new を使用しない場合は、コンパイラの警告が生成されます。詳細については、「Override キーワードと New キーワードによるバージョン管理 (C# プログラミング ガイド)」および「Override キーワードと New キーワードを使用する場合について (C# プログラミング ガイド)」を参照してください。