C#学習メモ - クラス継承、隠蔽と基底クラスメソッドのオーバーライド

1. クラス継承

継承を利用することで、既存のクラスを基礎として新しいクラスを定義し、機能を拡張できます。

  • 既存のクラスを基礎として新しいクラスを定義できます。既存のクラスは基底クラス(base class)と呼ばれ、新しいクラスは派生クラス(derived class)と呼ばれます。派生クラスのメンバーは以下の要素で構成されます:

    • クラス自身で宣言されたメンバー
    • 基底クラスのメンバー
  • 派生クラスを宣言するには、クラス名の後に基底クラスの指定を記述します:

    • 基底クラスの指定は、コロンと基底クラスの名前から構成されます
    • 派生クラスは指定された基底クラスを直接継承します
  • 派生クラスは基底クラスを拡張します。なぜなら、基底クラスのメンバーを含んでいるだけでなく、自身で宣言された新しい機能も持っているからです

  • 派生クラスは継承したメンバーを削除することはできません

class 派生クラス : 基底クラス
{
    // 新しいメンバーを追加
}

2. 継承されたメンバーへのアクセス

継承されたメンバーは、派生クラス自身が宣言したかのようにアクセスできます(継承されたコンストラクタは若干異なります)。

class 基底クラス
{
    public string 基底フィールド = "基底クラスのフィールド";
    
    public void 基底メソッド(string 値)
    {
        Console.WriteLine($"基底クラスのメソッド、出力: {値}");
    }
}
class 派生クラス : 基底クラス
{
    public string 派生フィールド = "派生クラスのフィールド";
    
    public void 派生メソッド(string 値)
    {
        Console.WriteLine($"派生クラスのメソッド、出力: {値}");
    }
}
static void Main(string[] args)
{
    派生クラス 派生インスタンス = new 派生クラス();
    派生インスタンス.基底メソッド(派生インスタンス.基底フィールド);
    派生インスタンス.基底メソッド(派生インスタンス.派生フィールド);
    派生インスタンス.派生メソッド(派生インスタンス.基底フィールド);
    派生インスタンス.派生メソッド(派生インスタンス.派生フィールド);
}

3. すべてのクラスはObjectクラスから継承

  • Objectクラスを除くすべてのクラスは派生クラスです。なぜなら、Objectクラスは継承階層の基礎だからです
  • 基底クラスの指定がない場合は、Objectクラスから暗黙的に継承されます
注意点
  1. クラス宣言における基底クラスの指定は一つのみでなければなりません。これを単一継承と呼びます
  2. 直接継承できるクラスは一つだけですが、派生の階層には制限がありません。基底クラスは別のクラスから派生し、そのクラスはさらに別のクラスから派生する可能性があり、最終的にObjectクラスに到達します

4. 基底クラスメンバーの隠蔽

派生クラスは継承したメンバーを削除することはできませんが、同じ名前のメンバーを使用して基底クラスのメンバーを隠蔽できます。

  • newキーワードを使用し、同じ名前で基底クラスのメンバーを隠蔽します
class 基底クラス
{
    public string フィールド;
}
class 派生クラス
{
    new public string フィールド; // newキーワードを使用して基底クラスのメンバーを隠蔽
}
注意点
  1. 継承されたデータメンバーを隠蔽するには、同じ型で同じ名前の新しいメンバーを宣言します
  2. 継承された関数メンバーを隠蔽するには、同じシグネチャ(戻り値の型は含まない)を持つ新しい関数メンバーを宣言します
  3. 継承されたメンバーを意図的に隠蔽していることをコンパイラに知らせるには、new修飾子を使用します。そうしないと、コンパイラは警告メッセージを表示します
  4. 静的メンバーも隠蔽できます
class 基底クラス
{
    public string フィールド = "基底クラスのフィールド";
    
    public void メソッド(string 値)
    {
        Console.WriteLine($"基底クラスのメソッド、出力: {値}");
    }
}
class 派生クラス : 基底クラス
{
    // newキーワードを使用して基底クラスのフィールドを隠蔽
    new public string フィールド = "派生クラスのフィールド、基底クラスの同名メソッドを隠蔽";
    
    // newキーワードを使用して基底クラスのメソッドを隠蔽
    new public void メソッド(string 値)
    {
        Console.WriteLine($"派生クラスのメソッド、基底クラスの同名メソッドを隠蔽、出力: {値}");
    }
}
static void Main(string[] args)
{
    派生クラス 派生インスタンス = new 派生クラス();
    派生インスタンス.メソッド(派生インスタンス.フィールド);
}

5. 基底クラスへのアクセス

派生クラスが隠蔽された継承メンバーにアクセスする必要がある場合、派生クラス内で基底クラスアクセス式を使用できます。

base.メソッド();
base.フィールド;
class 基底クラス
{
    public string フィールド = "基底クラスのフィールド";
    
    public void メソッド(string 値)
    {
        Console.WriteLine($"基底クラスのメソッド、出力: {値}");
    }
}
class 派生クラス : 基底クラス
{
    // newキーワードを使用して基底クラスのフィールドを隠蔽
    new public string フィールド = "派生クラスのフィールド、基底クラスの同名メソッドを隠蔽";
    
    // newキーワードを使用して基底クラスのメソッドを隠蔽
    new public void メソッド(string 値)
    {
        Console.WriteLine($"派生クラスのメソッド、基底クラスの同名メソッドを隠蔽、出力: {値}");
    }
    
    // 基底クラスへのアクセス
    public void 基底クラスを使用する例()
    {
        base.メソッド(base.フィールド);
    }
}
static void Main(string[] args)
{
    派生クラス 派生インスタンス = new 派生クラス();
    派生インスタンス.基底クラスを使用する例();
}

6. 基底クラスの参照

派生クラスのインスタンスは、基底クラスのインスタンスと派生クラスが追加したメンバーで構成されます。派生クラスの参照は、基底クラス部分を含む全体のオブジェクトを指します。

  • 派生クラスオブジェクトの参照がある場合、そのオブジェクトの基底クラス部分への参照を取得できます:

    派生クラス 派生インスタンス = new 派生クラス();
    基底クラス 基底参照 = (基底クラス)派生インスタンス; // 型変換演算子を使用して、派生クラスの参照を基底クラスの型に変換します
    
  • オブジェクトを基底クラスにキャストすると、その変数は基底クラスのメンバーのみにアクセスできます(オーバーライドされたメソッドを除く)

class 基底クラス
{
    public void 表示()
    {
        Console.WriteLine("基底クラスのメソッド");
    }
}
class 派生クラス : 基底クラス
{
    public int 変数1;
    new public void 表示()
    {
        Console.WriteLine("派生クラスのメソッド");
    }
}
static void Main(string[] args)
{
    派生クラス 派生インスタンス = new 派生クラス();
    基底クラス 基底参照 = (基底クラス)派生インスタンス;
    基底参照.表示(); // 基底クラスのメソッド
}

(1) 仮想メソッドとオーバーライドメソッド

仮想メソッドを使用すると、基底クラスの参照を通じて派生クラスのメソッドを呼び出すことができます。

基底クラスの参照を通じて派生クラスのメソッドを呼び出すには、以下の条件を満たす必要があります:

  • 派生クラスのメソッドと基底クラスのメソッドが同じシグネチャと戻り値の型を持つ
  • 基底クラスのメソッドがvirtualでマークされている
  • 派生クラスのメソッドがoverrideでマークされている
class 基底クラス
{
    virtual public void 表示()
    {
        Console.WriteLine("基底クラスのメソッド");
    }
}
class 派生クラス : 基底クラス
{
    override public void 表示()
    {
        Console.WriteLine("派生クラスのメソッド");
    }
}
注意: newキーワードとの違い
  • 基底クラスの参照を使用してメソッドを呼び出すと、メソッド呼び出しが派生クラスに渡され、実行されます。理由は以下の通りです:

    • 基底クラスのメソッドがvirtualとしてマークされている
    • 派生クラスに一致するoverrideメソッドがある
class 基底クラス
{
    virtual public void 表示()
    {
        Console.WriteLine("基底クラスのメソッド");
    }
}
class 派生クラス : 基底クラス
{
    public override void 表示()
    {
        Console.WriteLine("派生クラスのメソッド");
    }
}
static void Main(string[] args)
{
    派生クラス 派生インスタンス = new 派生クラス();
    基底クラス 基底参照 = (基底クラス)派生インスタンス;
    派生インスタンス.表示(); // 派生クラスのメソッド
    基底参照.表示(); // 派生クラスのメソッド
}
注意点
  1. オーバーライドメソッドとオーバーライドされるメソッドは、同じ可視性を持つ必要があります
  2. 静的メソッドまたは非仮想メソッドはオーバーライドできません
  3. メソッド、プロパティ、インデクサ、イベントはすべてvirtualおよびoverrideとして宣言できます

(2) overrideとしてマークされたメソッドのオーバーライド

オーバーライドメソッドは、継承階層の任意のレベルで出現できます。

  • オブジェクトの基底クラス部分の参照を使用してオーバーライドされたメソッドを呼び出すと、メソッド呼び出しが派生階層を上方向に伝播し、overrideとしてマークされた最も派生された(most-derived)バージョンまで実行されます
  • より高い派生レベルでメソッドの他の宣言があるが、overrideとしてマークされていない場合、それらは呼び出されません
検証例
// 基底クラス
class 基底クラス
{
    virtual public void 表示()
    {
        Console.WriteLine("基底クラスのメソッド");
    }
}
// 派生クラス
class 派生クラス : 基底クラス
{
    override public void 表示()
    {
        Console.WriteLine("派生クラスのメソッド");
    }
}
1)overrideを使用して最上位派生クラスの表示メソッドを宣言
class 第二派生クラス : 派生クラス
{
    override public void 表示()
    {
        Console.WriteLine("最上位派生クラスのメソッド");
    }
}

overrideメソッドは、2つの下位派生レベルのバージョンをオーバーライドします。基底クラスの参照を使用して表示を呼び出すと、最上位派生クラスのSecondDerivedまで上方向に伝播されます。

static void Main(string[] args)
{
    第二派生クラス 派生インスタンス = new 第二派生クラス();
    基底クラス 基底参照 = (基底クラス)派生インスタンス;
    
    派生インスタンス.表示(); // 最上位派生クラスのメソッド
    基底参照.表示(); // 最上位派生クラスのメソッド
}

結論

  • 派生クラスまたは基底クラスを通じて呼び出すかに関わらず、最も派生されたクラスのメソッドが呼び出されます
  • 基底クラスを通じて呼び出す場合、呼び出しが継承階層を上方向に伝播します
2)newを使用して最上位派生クラスの表示メソッドを宣言
class 第二派生クラス : 派生クラス
{
    // newを使用して最上位派生クラスの表示メソッドを宣言
    new public void 表示()
    {
        Console.WriteLine("最上位派生クラスのメソッド");
    }
}

mybcを使用して表示メソッドを呼び出すと、メソッド呼び出しが一レベル上に伝播し、派生クラスで実行され、オーバーライドされたメソッドを隠蔽します。

static void Main(string[] args)
{
    第二派生クラス 派生インスタンス = new 第二派生クラス();
    基底クラス 基底参照 = (基底クラス)派生インスタンス;
    
    派生インスタンス.表示(); // 最上位派生クラスのメソッド
    基底参照.表示(); // 派生クラスのメソッド
}

(3) 他のメンバーのオーバーライド

virtual/overrideは、メソッド、プロパティ、イベント、インデクサをオーバーライドできます。

例: 読み取り専用プロパティのオーバーライド
class 基底クラス
{
    public int _プロパティ = 5;
    virtual public int プロパティ
    {
        get { return _プロパティ; }
    }
}
class 派生クラス : 基底クラス
{
    // バッキングフィールドを隠蔽しないと、コンパイラはエラーを報告します(実行時には問題ありません)
    new private int _プロパティ = 10;
    override public int プロパティ
    {
        get { return _プロパティ; }
    }
}
static void Main(string[] args)
{
    派生クラス 派生インスタンス = new 派生クラス();
    基底クラス 基底参照 = 派生インスタンス;
    Console.WriteLine(派生インスタンス.プロパティ); // 10
    Console.WriteLine(基底参照.プロパティ); // 10
}

タグ: C# クラス継承 メソッドオーバーライド 仮想メソッド newキーワード

5月16日 02:42 投稿