次の方法で共有


継承 - より特殊な動作を作成するための型の派生

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

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

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

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

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

次の図は、一部のビジネス プロセスの作業項目を表すクラス WorkItem を示しています。 すべてのクラスと同様に、 System.Object から派生し、そのすべてのメソッドを継承します。 WorkItem は、独自の 6 つのメンバーを追加します。 これらのメンバーにはコンストラクターが含まれています。なぜなら、コンストラクターは継承されないからです。 クラス ChangeRequestWorkItem から継承され、特定の種類の作業項目を表します。 ChangeRequest は、 WorkItem から継承するメンバーと Objectから継承するメンバーにさらに 2 つのメンバーを追加します。 独自のコンストラクターを追加し、 originalItemIDも追加する必要があります。 プロパティ originalItemIDを使用すると、変更要求が適用される元のChangeRequestWorkItem インスタンスを関連付けられます。

クラスの継承を示す図

次の例は、前の図で示したクラスリレーションシップが 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 番目の主な特性であるポリモーフィズムの基礎です。 詳細については、「 ポリモーフィズム」を参照してください。

抽象基底クラス

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

インターフェイス

インターフェイスは、メンバーのセットを定義する参照型です。 そのインターフェイスを実装するすべてのクラスと構造体は、そのメンバーのセットを実装する必要があります。 インターフェイスでは、これらのメンバーのいずれかまたはすべての既定の実装を定義できます。 1 つの直接基底クラスからのみ派生できる場合でも、クラスは複数のインターフェイスを実装できます。

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

さらなる派生の防止

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

派生クラスによる基底クラスメンバーの非表示

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