C# 9.0のオブジェクト初期化子とInit Only Setter

C#にはオブジェクト初期化子(Objec Initializer)という構文があります。下記のように記述することでクラスのプロパティを初期化できるというものです。

Cat cat = new Cat { Age = 10, Name = "Fluffy" };
Cat sameCat = new Cat("Fluffy"){ Age = 10 };

System.Console.WriteLine($"cat.Age = {cat.Age}, cat.Name = {cat.Name}");
// -> cat.Age = 10, cat.Name = Fluffy
System.Console.WriteLine($"sameCat.Age = {sameCat.Age}, sameCat.Name = {sameCat.Name}");
// -> sameCat.Age = 10, sameCat.Name = Fluffy

public class Cat
{
    // Auto-implemented properties.
    public int Age { get; set; }
    public string Name { get; set; }

    public Cat()
    {
    }

    public Cat(string name)
    {
        this.Name = name;
    }
}

(余談ですが上記コードはC#9のTop-level statementsでは導入されると有効なソースコードです。)

この構文はコンストラクタを呼び出したあとにプロパティに値を代入する、下記のコードのシンタックスシュガーです(下記はILをデコンパイルしたC#コード)。なぜ初期化子という名前がつけられているのかな、と個人的には思っていました。

Cat cat = new Cat();
cat.Age = 10;
cat.Name = "Fluffy";
Cat cat2 = cat;
Cat cat3 = new Cat("Fluffy");
cat3.Age = 10;
Cat cat4 = cat3;
Console.WriteLine(string.Format("cat.Age = {0}, cat.Name = {1}", cat2.Age, cat2.Name));
Console.WriteLine(string.Format("sameCat.Age = {0}, sameCat.Name = {1}", cat4.Age, cat4.Name));

そんなことを思い、雑につぶやいていたら@neueccに下記のようなツッコミをいただきました。

なるほど?と思い、調べてみました。

Init Only Setter

C# 9.0から、Init Only Setterという構文が追加されました。この構文は、主に不変な型を定義をより楽に行うためのものです。

C# 1.0から不変なデータを実装するには、2通りの方法しか提供されていません。

  1. readonlyでフィールドの定義する
  2. getterプロパティのみを定義する

例えば1.を用いると、下記のように不変データを実装できます。

var point = new Point(1, 2);
point.X = 3;
// -> error CS0200: Property or indexer 'Point.X' cannot be assigned to -- it is read only

struct Point
{
    public int X { get; }
    public int Y { get; }

    public Point(int x, int y)
    {
        this.X = x;
        this.Y = y;
    }
}

ただし上記コードではプロパティの初期化に、それぞれコンストラクタでデータを受け取って、それをプロパティに代入するというボイラープレート的なコードを毎度記述しなければならないという問題がありました。Init Only Setterはこれを解決するために導入されました。

具体的には下記ようなコードを書けます。

var point = new Point() { X = 1, Y = 2, };
point.X = 3;
// -> error CS8852: Init-only property or indexer 'Point.X' can only be assigned in an object initializer, or on 'this' or 'base' in an instance constructor or an 'init' accessor.

struct Point
{
    public int X { get; init; }
    public int Y { get; init; }
}

わざわざボイラープレートなコードを書くことなくプロパティを初期化することができました。このようにinitと定義するとそのプロパティをInit Only Setterとして定義できます。また、Init Only Setterなプロパティを初期化できるのがオブジェクト初期化子です(厳密には他の初期化方法も存在します)。

まとめ

C# 9.0のInit Only Setterの導入により、(個人的には)オブジェクト初期化子はその名にふさわしい機能になりました。

参考