継承 - 型を派生させて特殊な動作を作成する

継承は、カプセル化およびポリモーフィズムと共に、オブジェクト指向プログラミングの主要な 3 つの特性の 1 つです。 継承を使用すると、他のクラスで定義されている動作を再利用、拡張、変更して新しいクラスを作成できます。 メンバーが継承される側のクラスを "基底クラス" と呼び、メンバーを継承する側のクラスを "派生クラス" と呼びます。 派生クラスは、直接の基底クラスを 1 つだけ持つことができます。 ただし、継承は推移的です。 ClassCClassB から派生し、ClassBClassA から派生している場合、ClassC では、ClassBClassA で宣言されているメンバーが継承されます。

注意

構造体では、継承がサポートされていませんが、インターフェイスを実装することはできます。

概念的には、派生クラスは基底クラスから特化したクラスです。 たとえば、基底クラス Animal がある場合、Mammal という名前の派生クラスと、Reptile という名前の別の派生クラスを持つことができます。 MammalAnimal であり、ReptileAnimal ですが、各派生クラスは、基底クラスから別々の特殊化を表します。

インターフェイス宣言では、そのメンバーの既定の実装を定義できます。 そのような実装は、派生インターフェイスと派生インターフェイスを実装するクラスによって継承されます。 既定のインターフェイス メソッドの詳細については、インターフェイスに関する記事をご覧ください。

クラスを別のクラスから派生するように定義すると、派生クラスには、コンストラクターとファイナライザーを除く、基底クラスのすべてのメンバーが暗黙的に引き継がれます。 派生クラスでは、基底クラスのコードを再実装することなく再利用します。 派生クラスでは、メンバーを追加できます。 派生クラスでは、基底クラスの機能が拡張されます。

次の図は、あるビジネス プロセスの作業項目を表す WorkItem クラスを示しています。 他のすべてのクラスと同様に、System.Object から派生し、そのすべてのメソッドを継承します。 WorkItem には、独自の 6 つのメンバーが追加されています。 そのようなメンバーにはコンストラクターが含まれます。コンストラクターは継承されないためです。 WorkItem から継承される ChangeRequest クラスは、特定の種類の作業項目を表します。 ChangeRequest には、WorkItemObject から継承したメンバーに 2 つのメンバーが追加されます。 独自のコンストラクターを追加する必要があるほか、さらに originalItemID も追加されます。 originalItemID プロパティを使用すると、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 として宣言される場合、そのクラスから直接継承されるあらゆる非抽象クラスでそのメソッドをオーバーライドする必要があります。 派生クラス自体が抽象クラスである場合は、抽象メンバーを実装することなく継承します。 抽象メンバーと仮想メンバーは、オブジェクト指向プログラミングの重要な特性の 2 つ目であるポリモーフィズムの基礎です。 詳細については、「ポリモーフィズム」を参照してください。

抽象基底クラス

new 演算子を使用して直接インスタンス化されないようにする場合は、クラスを abstract として宣言できます。 抽象クラスは、新しいクラスがそれから誘導される場合にのみ利用できます。 抽象クラスには、それ自体が abstract として宣言された 1 つ以上のメソッド シグネチャを含めることができます。 これらのシグネチャは、パラメーターと戻り値を指定しますが、実装 (メソッドの本体) は持ちません。 抽象クラスには抽象メンバーを含める必要がありません。ただし、クラスに抽象メンバーが含まれる場合、そのクラス自体を抽象として宣言する必要があります。 それ自体が抽象ではない派生クラスの場合、抽象基底クラスから抽象メソッドを実装する必要があります。

インターフェイス

"インターフェイス" は、一連のメンバーを定義する参照型です。 そのインターフェイスを実装するすべてのクラスと構造体で、その一連のメンバーを実装する必要があります。 インターフェイスでは、それらのメンバーの一部または全部に対して既定の実装を定義できます。 クラスは、直接的には 1 つの基底クラスからしか派生できませんが、複数のインターフェイスを実装できます。

インターフェイスは、"is a" 関係を必ずしも持たないクラスに特定の機能を定義する目的で使用されます。 たとえば、System.IEquatable<T> インターフェイスをクラスまたは構造体によって実装し、特定の型の 2 つのオブジェクトが等しいかどうかを判断できます (もっとも等価性は型で定義されます)。 IEquatable<T> では、基底クラスと派生クラスの間に存在する同じ種類の "is a" 関係 (たとえば、MammalAnimal である) を意味しません。 詳細については、「インターフェイス」を参照してください。

後続の派生を禁止する

クラスでは、それ自体かメンバーを sealed として宣言することで、他のクラスがそのクラスまたはそのメンバーから継承することを禁止できます。

派生クラスで基底クラス メンバーを隠ぺいする

派生クラスは、同じ名前とシグネチャでメンバーを宣言することで、基底クラスのメンバーを隠ぺいすることができます。 new 修飾子を利用すると、メンバーが基底メンバーのオーバーライドではないことを明示的に示すことができます。 new の使用は必須ではありませんが、new が使用されていない場合、コンパイラの警告が生成されます。 詳細については、「Override キーワードと New キーワードによるバージョン管理」および「Override キーワードと New キーワードを使用する場合について」を参照してください。