【C#】init専用プロパティの使い方|イミュータブルなオブジェクト設計

C# 9.0 以降では init アクセサが導入され、オブジェクト初期化子でのみ値を設定できる「init専用プロパティ」を定義できるようになりました。これにより、オブジェクトの生成後は値を変更できない「イミュータブル(不変)」な設計を簡単に実現できます。本記事では init 専用プロパティの基本と実用例を紹介します。

従来のプロパティとの違い

従来の set アクセサを持つプロパティは、オブジェクト生成後でも値を変更できました。

public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}

var p = new Person { Name = "Taro", Age = 20 };
p.Age = 21; // 生成後でも変更可能

しかし業務システムなどでは「生成後に変更できない値」を扱いたいケースも多く、init が登場しました。

init専用プロパティの基本

init を使うと、オブジェクト初期化時にのみ設定可能で、それ以降は変更できません。

public class Person
{
    public string Name { get; init; }
    public int Age { get; init; }
}

var p = new Person { Name = "Hanako", Age = 18 };
// p.Age = 19; // コンパイルエラー(変更不可)

コンストラクタと組み合わせる

init プロパティはコンストラクタと組み合わせても便利です。必須項目をコンストラクタで設定し、オプション項目を初期化子で指定すると柔軟性が増します。

public class Product
{
    public string Id { get; }
    public string Name { get; init; }
    public int Price { get; init; }

    public Product(string id)
    {
        Id = id; // 必須プロパティはコンストラクタで設定
    }
}

var item = new Product("P001") { Name = "Book", Price = 1000 };

レコード型とinit

C# 9.0 で導入されたレコード型はデフォルトで init 専用プロパティを持ち、イミュータブルデータモデルの設計に適しています。

public record Employee
{
    public string Name { get; init; }
    public int Age { get; init; }
}

var e1 = new Employee { Name = "Ken", Age = 30 };
// e1.Age = 31; // コンパイルエラー

init専用プロパティを使う利点

  • オブジェクト生成後の不変性を保証できる
  • 値オブジェクトやDTOの設計に最適
  • 初期化子を使った柔軟な代入が可能
  • レコード型と組み合わせると宣言がさらに簡潔になる

まとめ

init 専用プロパティはイミュータブルなオブジェクト設計を簡単に実現するための重要な機能です。

  • set の代わりに init を使うことで生成後の変更を禁止
  • 必須値はコンストラクタ、任意値は初期化子で設定できる
  • レコード型と組み合わせるとシンプルで安全なデータモデルを構築できる

「変更できないこと」が品質向上につながるケースでは、積極的に init 専用プロパティを採用しましょう。