sh1’s diary

プログラミング、読んだ本、資格試験、ゲームとか私を記録するところ

C# init アクセサー (C#9.0) の使い方

C# 9.0 から init アクセサーが追加されています。比較的に使いやすいアクセサーですが、利用しているでしょうか。仕様を把握しているでしょうか。

これまでの流れ

getter/setter で記述するパターンは、(アクセス修飾子を除くと)だいたいこんな感じの流れがありました。

  1. get; set; (C# 3.0)
  2. get; (C# 6.0)
  3. get; init; (C# 9.0)

一番の基本形となるパターン1で戸惑うことは無いけど、2と3の違いを把握できていますか。そこが init アクセサーを追加したポイントになると思います。先に違いの要点を整理すると下図のとおり:

変数 コンストラクター オブジェクト初期化子 メソッド
1.get; set;
2.get; × ×
3.get; init; ×

パターンの1のように、メソッドからアクセスできてしまうと、値変更のアクセスに制限はありません。パターン2は、値変更がコンスタクター内でしかできなかったため、かなり厳しい制限です。つまり、get; init; が必要になった理由は get; のみだと値を初期化する制限がコーディングする上で厳しすぎるという雰囲気になり、ちょうどいい値の初期化制限を実現するために生まれたものです。

アクセス修飾子 (private, protected) は class 外からのアクセス制限をするものなので、目的が異なります。(コンストラクターを private にして、singleton パターンを実装することもあるので、初期化と関係が無いわけでもありませんが)

一番の違いはオブジェクト初期化子

オブジェクト初期子でのデータの書き換えは、下のような場合です。SharpLab でも書いてみました。

var data = new SampleData
{
    Data = 100
};

record 型専用の初期化 with

C# 9.0 から with 式を利用できるようになりました。with 式は、一般的なクラスの値初期化で利用できるキーワードではなくて、データの読み書きに特化した record 型のときに利用できるキーワードです。(C# 10.0 以降は、構造体と匿名型も対象ですが、利用目的は基本同じ)

using System;

public class Program
{
    record Sample
    {
        public string Name { get; init; }
    }
    
    public static void Main()
    {
        var program = new Program();
            
        program.Run();
    }
    
    public void Run()
    {
        var sample1 = new Sample
        {
            Name = "ハルウララ"
        };
        
        var sample2 = sample1 with { Name = "キングヘイロー" };
        
        Console.WriteLine($"Data value = {sample1.Name}.");
        Console.WriteLine($"Data value = {sample2.Name}.");
    }
}
Data value = ハルウララ.
Data value = キングヘイロー.

その他に readonly フィールドの書き換えが init アクセサー内で可能です。ただ、ケースとして珍しいと思うので記述していません。

++C++; - プロパティ」の解説では、以下のような意見があります。

歴史的経緯で init という新キーワードが使われていますが、もし C#フルスクラッチで作り直せるなら readonly が最初から init 相当の仕様になっていたと思います。

そんなわけで、init を使ってみるのはそんなに怖いものではないです。

参考