プログラムを中心とした個人的なメモ用のブログです。 タイトルは迷走中。
内容の保証はできませんのであしからずご了承ください。

2020/07/13

[C#] コンストラクタで base と this を両方使いたい

event_note2020/07/13 8:50

コンストラクタにおいて、基底クラスのコンストラクタを指定したい場合は base を、同じクラス内の別のコンストラクタを指定したい場合は this を指定しますが、両方指定したい場合はどうすればいいのか?

結論から先に書くと、両方を指定することはできません。

恐らくですが、basethis を両方指定したいと思うということは、コンストラクタ内で行う処理を全て共通化したいということなのだと思います。
私の場合はそうでした。

そして、私の場合はデフォルト引数を使うことで解決しました。

また、basethis を指定した場合(または指定しなかった場合)に、どのようにコンストラクタが呼ばれるかをきちんと把握すれば、もしかしたらどちらかを指定するだけで事足りるかもしれません。

というわけでいろいろ試してみました。

環境

  • Visual Studio 2019
  • .NET Core 3.1

サンプル1

基底クラス SampleBase と、派生クラス Sample に、それぞれ引数なしと引数ありのコンストラクタを作成しています。

パターン1

basethis も指定しない場合です。

public static void Main()
{
    new Sample();
    Console.WriteLine("----------");
    new Sample("foo");
}

/// <summary>
/// 基底クラス
/// </summary>
abstract class SampleBase
{
    public SampleBase()
    {
        Console.WriteLine("SampleBase コンストラクタ引数なし");
    }

    public SampleBase(string foo)
    {
        Console.WriteLine($"SampleBase コンストラクタ引数あり:{foo}");
    }
}

/// <summary>
/// 派生クラス
/// </summary>
class Sample : SampleBase
{
    public Sample()
    {
        Console.WriteLine("Sample コンストラクタ引数なし");
    }

    public Sample(string foo)
    {
        Console.WriteLine($"Sample コンストラクタ引数あり:{foo}");
    }
}

出力結果

SampleBase コンストラクタ引数なし
Sample コンストラクタ引数なし
----------
SampleBase コンストラクタ引数なし
Sample コンストラクタ引数あり:foo

派生クラス Sample は引数の有無によって呼ばれるコンストラクタが違いますが、基底クラス SampleBase はいずれの場合も引数なしのコンストラクタが呼ばれています。

パターン2

パターン1をベースに、基底クラスの引数ありコンストラクタを使うために、派生クラス Samplebase を指定しています。

public static void Main()
{
    new Sample();
    Console.WriteLine("----------");
    new Sample("foo");
}

/// <summary>
/// 基底クラス
/// </summary>
abstract class SampleBase
{
    public SampleBase()
    {
        Console.WriteLine("SampleBase コンストラクタ引数なし");
    }

    public SampleBase(string foo)
    {
        Console.WriteLine($"SampleBase コンストラクタ引数あり:{foo}");
    }
}

/// <summary>
/// 派生クラス
/// </summary>
class Sample : SampleBase
{
    public Sample()
    {
        Console.WriteLine("Sample コンストラクタ引数なし");
    }

    public Sample(string foo) : base(foo)
    {
        Console.WriteLine($"Sample コンストラクタ引数あり:{foo}");
    }
}

出力結果

SampleBase コンストラクタ引数なし
Sample コンストラクタ引数なし
----------
SampleBase コンストラクタ引数あり:foo
Sample コンストラクタ引数あり:foo

引数を指定しなかった場合は、Sample SampleBase ともに引数なしのコンストラクタがコールされます。 引数を指定した場合は、Sample SampleBase ともに引数ありのコンストラクタがコールされます。

まぁここらへんまでは当たり前ですね。

パターン3

パターン2をベースに、基底クラス SampleBase の引数ありコンストラクタで this を指定しています。

public static void Main()
{
    new Sample();
    Console.WriteLine("----------");
    new Sample("foo");
}

/// <summary>
/// 基底クラス
/// </summary>
abstract class SampleBase
{
    public SampleBase()
    {
        Console.WriteLine("SampleBase コンストラクタ引数なし");
    }

    public SampleBase(string foo) : this()
    {
        Console.WriteLine($"SampleBase コンストラクタ引数あり:{foo}");
    }
}

/// <summary>
/// 派生クラス
/// </summary>
class Sample : SampleBase
{
    public Sample()
    {
        Console.WriteLine("Sample コンストラクタ引数なし");
    }

    public Sample(string foo) : base(foo)
    {
        Console.WriteLine($"Sample コンストラクタ引数あり:{foo}");
    }
}

出力結果

SampleBase コンストラクタ引数なし
Sample コンストラクタ引数なし
----------
SampleBase コンストラクタ引数なし
SampleBase コンストラクタ引数あり:foo
Sample コンストラクタ引数あり:foo

引数を指定した場合、基底クラスのコンストラクタは両方呼ばれます。

パターン4

パターン3をベースに、派生クラス Sample の引数なしコンストラクタで基底クラス SampleBase の引数ありコンストラクタを呼んでいます。

public static void Main()
{
    new Sample();
    Console.WriteLine("----------");
    new Sample("foo");
}

/// <summary>
/// 基底クラス
/// </summary>
abstract class SampleBase
{
    public SampleBase()
    {
        Console.WriteLine("SampleBase コンストラクタ引数なし");
    }

    public SampleBase(string foo) : this()
    {
        Console.WriteLine($"SampleBase コンストラクタ引数あり:{foo}");
    }
}

/// <summary>
/// 派生クラス
/// </summary>
class Sample : SampleBase
{
    public Sample() : base(null)
    {
        Console.WriteLine("Sample コンストラクタ引数なし");
    }

    public Sample(string foo) : this()
    {
        Console.WriteLine($"Sample コンストラクタ引数あり:{foo}");
    }
}

出力結果

SampleBase コンストラクタ引数なし
SampleBase コンストラクタ引数あり:
Sample コンストラクタ引数なし
----------
SampleBase コンストラクタ引数なし
SampleBase コンストラクタ引数あり:
Sample コンストラクタ引数なし
Sample コンストラクタ引数あり:foo

引数を指定した場合は全てのコンストラクタが呼ばれています。

多分これが一番問題のパターン。

派生クラス Sample の引数の値を基底クラス SampleBase に渡しつつ、コンストラクタ内の処理は各クラスの引数なしコンストラクタに集約したい。

しかし、派生クラス Sample の引数ありコンストラクタで basethis の両方を指定することはできません。

かと言って、このパターンのようにやると this を指定したときに引数の情報が失われてしまいます(当たり前ですが)。

というわけで、代替案が次のサンプルです。

サンプル2(代替案)

派生クラス Sample のコンストラクタを引数ありの1つのみにし、デフォルト引数を定義する方法です。

public static void Main()
{
    new Sample();
    Console.WriteLine("----------");
    new Sample("foo");
}

/// <summary>
/// 基底クラス
/// </summary>
abstract class SampleBase
{
    public SampleBase()
    {
        Console.WriteLine("SampleBase コンストラクタ引数なし");
    }

    public SampleBase(string foo) : this()
    {
        Console.WriteLine($"SampleBase コンストラクタ引数あり:{foo}");
    }
}

/// <summary>
/// 派生クラス
/// </summary>
class Sample : SampleBase
{
    public Sample(string foo = null) : base(foo)
    {
        Console.WriteLine($"Sample コンストラクタ引数あり:{foo}");
    }
}

出力結果

SampleBase コンストラクタ引数なし
SampleBase コンストラクタ引数あり:
Sample コンストラクタ引数あり:
----------
SampleBase コンストラクタ引数なし
SampleBase コンストラクタ引数あり:foo
Sample コンストラクタ引数あり:foo

私の場合はこれで解決しました。