init
アクセサは大変良いです。C# 9.0 で追加された初期化のタイミングでのみプロパティに値を設定できる set
アクセサです。アクセシビリティは狭ければ狭いほどコードは安全になるので、僕は set
を見たらとりあえず init
に置き換える勢い!
.NET 5 未満で init
を利用する
init
アクセサは内部的にはただの set
アクセサです。set
アクセサに対して IsExternalInit
という型と共に modreq
という謎の (?) 修飾を行うことでコンパイラが init
な挙動と解釈してくれます。逆に言うと IsExternalInit
という型があり、それを解釈できるコンパイラがいれば利用できるとも言えます。
ということで .NET 5 以外のプロジェクトに対して IsExternalInit
型を準備します。.NET 5 で用意されている型は public
ですが、プロジェクト内だけに閉じる場合は internal
な型で大丈夫です。
#if !NET5_0_OR_GREATER // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. namespace System.Runtime.CompilerServices { internal sealed class IsExternalInit { } } #endif
あとは C# 9.0 を明示的に有効化すれば OK です。
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <!-- これで .NET 5 以外のターゲットに対しても init が有効になる --> <TargetFrameworks>netstandard2.0;netstandard2.1;net461;net5</TargetFrameworks> <LangVersion>9.0</LangVersion> </PropertyGroup> </Project>
readonly struct
の制約緩和
個人的にこれの何が嬉しいかと言うと、C# 7.2 から導入された readonly struct
の条件緩和があります。init
でコンパイラがアクセシビリティをより細かく制御できるようになったおかげで、以下のように書けるようになりました。
// C# 8.0 までは「getter only + コンストラクタ」しか認められなかった public readonly struct Person { public string Name { get; } public Person(string name) => this.Name = name; }
// C# 9.0 からは init でも大丈夫 public readonly struct Person { public string Name { get; init; } }
C# 9.0 が利用できるコンパイラさえあれば (= 最新の Visual Studio を利用してさえいれば)、Target Framework が .NET 5 でなくても init
を利用できるのは大きなメリットです。