コンストラクタ

定義

class MyClass
{
    int a = 1;
    const int b = 1;

    public MyClass()
    {
    }
}

多重定義 (オーバーロード)

多重定義したコンストラクタから他のコンストラクタを呼ぶには、thisキーワードを用いてイニシャライザのように記述します。

class MyClass
{
    public MyClass(            ) : this(0, 0, null) {} // 他のコンストラクタを呼ぶだけ
    public MyClass(int x, int y) : this(x, y, null) {} // 他のコンストラクタを呼ぶだけ

    public MyClass(int x, int y, string s)
    {
        // コンストラクタの実装
    }
}
10.10.6 省略可能なインスタンス コンストラクタ パラメータ (C#) | MSDN

コピー コンストラクタ

class MyClass
{
    int a;
    int b;

    public MyClass(int a, int b)
    {
        this.a = a;
        this.b = b;
    }

    // コピー コンストラクタ
    public MyClass(MyClass previous) // 自身と同じ型を引数に取る
    {
        // すべてのフィールドをコピーする
        this.a = previous.a;
        this.b = previous.b;
    }
}
MyClass myClass1 = new MyClass(1, 2);
MyClass myClass2 = new MyClass(myClass1); // コピー コンストラクタの呼び出し

他の型を継承しているときは、それのフィールドもすべてコピーする必要があります。クラスがsealedでなければ、record classの使用を検討します。コピー コンストラクターを記述する方法 - C# プログラミング ガイド - C# | Microsoft Learn

継承 (Inheritance)

派生クラスから基本クラスのコンストラクタを呼ぶには、baseキーワードを用います。

baseで呼び出すコンストラクタを明示しない場合は、引数のないコンストラクタが呼ばれます。基本クラスに引数のないコンストラクタが定義されていないときは、baseで明示しなければCS7036のエラーとなります。

class BaseClass
{
    public BaseClass() {}
    public BaseClass(int a) {}
}

class SubClass : BaseClass
{
    public SubClass() : this(1, 2)
    {
        // BaseClass() が呼ばれてから、SubClass(int, int) が呼ばれる
    }

    public SubClass(int a) : base(a)
    {
        // BaseClass(int) が呼ばれる
    }

    public SubClass(int a, int b)
    {
        // BaseClass() が呼ばれる
    }
}

静的コンストラクタ (static constructor)

静的コンストラクタは、

  • タイプコンストラクタ (type constructor)
  • クラスコンストラクタ (class constructor)
  • タイプイニシャライザ (type initializer)

とも呼ばれます。この静的コンストラクタにはアクセス修飾子やパラメータは許されず、static ClassName()の構文のみです。

class MyClass
{
    static readonly int a;

    static MyClass()
    {
        a = 1;
    }

    public MyClass()
    {
        a = 1; // error CS0198: 静的読み取り専用フィールドへの割り当てはできません (静的コンストラクターまたは変数初期化子では可)。
    }
}
静的コンストラクター (C# プログラミング ガイド) | Microsoft Learn

静的フィールドは宣言時に初期化するようにして、これを初期化するために静的コンストラクタを用いないようにします。「参照型の静的フィールドをインラインで初期化します(Initialize reference type static fields inline)」としてCA1810で警告されます。

オブジェクトの生成

new演算子

new 演算子 - C# リファレンス | Microsoft Learn

MyClass obj  = new MyClass();

匿名型のインスタンスは、次のように生成できます。

var x = new { a = 1, b = "B" };
var x =
    from s in source
    select new {a = s.X};

オブジェクト初期化子 (Object initializers)

オブジェクト初期化子を用いると、アクセス可能なフィールドプロパティにオブジェクトの生成時に値を割り当てられます。 オブジェクト初期化子 - オブジェクト初期化子とコレクション初期化子 - C# プログラミング ガイド | Microsoft Learn 構文:インスタンス化と同時にプロパティを設定するには?[C#/VB]:.NET TIPS - @IT 山本康彦 (2016/02/24)

たとえば次のようにクラスが定義されているとき、

class MyClass
{
    private int a;
    public int b;

    public int C { get; set; }
}

通常ならばオブジェクトの生成後に値を割り当てる処理を、

MyClass obj1 = new MyClass();
obj1.b = 1;
obj1.C = 2;

オブジェクトの生成時に割り当てられます。

MyClass obj2 = new MyClass()
{
  //a = 0, // error CS0122: 'MyClass.a' はアクセスできない保護レベルになっています
    b = 1,
    C = 2
};

このとき引数のないコンストラクタを呼ぶならば、コンストラクタのかっこを省略できます。

MyClass obj3 = new MyClass
{
    b = 1,
    C = 2
};

不当なメンバへ割り当てようとすると、CS0747となります。

usingでのオブジェクト初期化子の使用

usingステートメントで生成するクラスのメンバもオブジェクト初期化子で割り当てられますが、そこで例外が投げられるとDispose()が呼ばれないため、usingステートメントではオブジェクト初期化子を使用すべきではありませんc# - Object initializers in using-block generates code analysis warning CA2000 - Stack Overflow

たとえば次のようにクラスが定義されているとき、

class MyClass : IDisposable
{
    private bool disposedValue = false;
    public int A
    {
        get;
        set;
    }

    protected virtual void Dispose(bool disposing)
    {
        if (!disposedValue) disposedValue = true;
    }

    public void Dispose()
    {
        Dispose(true);
    }
}

これにオブジェクト初期化子を用いるには、

using (MyClass obj = new MyClass() { A = 1 })
{
}

のように記述します。これはコンパイル時には次のように展開されます。

MyClass obj = new MyClass() { A = 1 }; // ここで例外が投げられると、finally句は実行されない

try
{
}
finally
{
    if (obj != null) ((IDisposable)obj).Dispose();
}

このようなコードに対しては、コード分析で「メソッド '***()' で、オブジェクト 'new MyClass()' が破棄されない例外パスがあります。オブジェクト 'new MyClass()' への参照がすべてスコープ外になる前に、このオブジェクトの System.IDisposable.Dispose を呼び出してください。」としてCA2000で警告されます。

よってIDisposableを継承しているクラスには、オブジェクト初期化子を用いないようにします。

コレクション初期化子 (collection initializers)

IEnumerableを実装し、Add()メソッドがあるコレクションで利用できます。コレクション初期化子 - オブジェクト初期化子とコレクション初期化子 - C# プログラミング ガイド | Microsoft Learn

List<int> list = new List<int> { 1, 2, 3 };
Queue<int> queue = new Queue<int> { 1, 2, 3 }; // error CS1061: 'Queue<int>' に 'Add' の定義が含まれておらず、型 'Queue<int>' の最初の引数を受け付ける拡張メソッド 'Add' が見つかりませんでした。using ディレクティブまたはアセンブリ参照が不足していないことを確認してください。

読み書き可能なインデクサがあるならば、インデックスを指定して値を指定できます。

Dictionary<int, string> dictionary = new Dictionary<int, string>
{
    [0] = "a",
    [3] = "b",
    [5] = "c",
};

コンストラクタのかっこは省略できます。コンストラクタで初期値を与えた場合は、それが先に格納されます。

List<int> list1 = new List<int> { 1, 2, 3 };
List<int> list2 = new List<int>() { 1, 2, 3 };
List<int> list3 = new List<int>(new[] { 5, 6, 7 }) { 1, 2, 3 }; // 5, 6, 7, 1, 2, 3 の順で格納される

メソッドから、破棄が必要なオブジェクトを返す方法

メソッドから破棄が必要なオブジェクトを返すとき、それを返す前に例外が投げられるとDispose()を呼ぶ手段がなくなるため、CA2000で警告されます。

public StreamReader Open()
{
    StreamReader result = new StreamReader("samle.txt");
    result.Read(); // ここで例外が投げられるとこのオブジェクトは破棄されないため、CA2000で警告される

    return result;
}

よって例外が投げられたときはメソッド内でDispose()を呼ぶようにします。

public StreamReader Open()
{
    StreamReader temp = null;
    StreamReader result = null;

    try
    {
        temp = new StreamReader("samle.txt");
        temp.Read(); // ここで例外が投げられる恐れがある

        // 例外が投げられる処理をすべて完了したら結果にそれを格納し、一時変数をnullとする
        result = temp;
        temp = null;
    }
    finally
    {
        // 例外が投げられたときは一時変数はnullになっておらず、結果はnullのまま
        if (temp != null)
        {
            temp.Dispose();
        }
    }

    return result;
}

コンストラクタが例外を投げないならば、次のように例外が投げられる処理をtry句で囲むことでも破棄できます。

public StreamReader Open()
{
    StreamReader result = new StreamReader("samle.txt");
    try
    {
        result.Read();
    }
    catch
    {
        result.Dispose();
        throw;
    }
    return result;
}

ただし例外を捕捉して対処できないならば、オブジェクトを破棄しても無意味です。

参考

参考書

Microsoft Learnから検索