xin9le.net

Microsoft の製品/技術が大好きな Microsoft MVP な管理人の技術ブログです。

.NET 5 未満でも C# 9.0 の init アクセサを利用する

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 を利用できるのは大きなメリットです。