Start Debugging

C# 14: field キーワードと field によって裏付けられたプロパティ

C# 14 はプロパティのアクセサー向けに文脈依存キーワード field を導入し、別途バッキングフィールドを宣言せずに自動プロパティへカスタムロジックを追加できるようにします。

C# 14 は新しい文脈依存キーワード field を導入します。これはプロパティのアクセサー (getsetinit ブロック) の中で、プロパティのバッキング ストレージを指すために使えます。簡単に言えば、field はプロパティの値が格納される隠れた変数を表すプレースホルダーです。このキーワードを使うと、別の private なフィールドを手で宣言することなく、自動実装プロパティにカスタムロジックを加えることができます。最初は C# 13 のプレビューで使えるようになり (.NET 9 で言語バージョンを preview に設定する必要がありました)、C# 14 で正式に言語の一部になりました。

なぜ便利なのか? C# 14 より前は、プロパティにロジック (バリデーションや変更通知など) を加えたい場合、フルプロパティに変えて private なバッキングフィールドを用意する必要がありました。これは定型コードを増やし、他のクラスメンバーがその private フィールドに直接アクセスしてプロパティのロジックを回避するリスクももたらしました。新しい field キーワードは、コンパイラーがバッキングフィールドの生成と管理を引き受ける一方で、あなたはプロパティのコードで field を使うだけ、という形でこれらの問題を解消します。結果として、より明快でメンテナンス性の高いプロパティ宣言になり、バッキング ストレージがクラスの他のスコープへ “漏れ出す” こともなくなります。

field のメリットとユースケース

field キーワードは、プロパティ宣言をより簡潔でエラーが起きにくいものにするために導入されました。主なメリットと有用なシナリオは次のとおりです。

全体として、field は冗長なコードを取り除き、必要なカスタム動作だけに集中することで、可読性とメンテナンス性を高めます。概念的には、setter での value キーワード (代入される値を表す) の動きに似ています。ここでは field がプロパティの内部ストレージを表します。

比較: 手書きのバッキングフィールド vs. field キーワード

違いを見るために、何らかのルールを強制するプロパティを C# 14 以前以後 (新しい field キーワードを使った場合) でどう宣言するか比較してみましょう。

シナリオ: プロパティ Hours を、決して負の数に設定できないようにしたいとします。古い C# では次のようにしていました。

C# 14 以前: 手書きのバッキングフィールドを使う場合:

public class TimePeriodBefore
{
    private double _hours;  // backing field

    public double Hours
    {
        get { return _hours; }
        set 
        {
            if (value < 0)
                throw new ArgumentOutOfRangeException(nameof(value), "Value must not be negative");
            _hours = value;
        }
    }
}

この C# 14 以前のコードでは、値を保持するために private フィールド _hours を導入する必要がありました。プロパティのゲッターはこのフィールドを返し、セッターは _hours への代入前にチェックを行います。これは動作しますが冗長です。_hours を宣言・管理するための余分なコードがあり、_hours はクラス内のどこからでもアクセスできます (つまり、他のメソッドが不注意に _hours に書き込んで、バリデーションロジックを回避する 可能性 があります)。

C# 14 以降: field キーワードを使う場合:

public class TimePeriod
{
    public double Hours
    {
        get;  // auto-implemented getter (compiler provides it)
        set => field = (value >= 0) 
            ? value 
            : throw new ArgumentOutOfRangeException(nameof(value), "Value must not be negative");
    }
}

ここでは、プロパティ Hours は明示的なバッキングフィールドなしで宣言されています。get; を本体なしで使い、自動ゲッターを示し、set には field を使う本体を与えています。セッター内の式 field = ... は、コンパイラーに「プロパティのバッキングフィールドへ代入せよ」と伝えます。コンパイラーは舞台裏で自動的に private フィールドを生成し、get アクセサーがそのフィールドを返すように実装します。上の setter では、value が負なら例外を投げ、そうでなければ field (それを保持する) に代入します。_hours自分で 宣言する必要はなく、ゲッターの本体を書く必要もありません。コンパイラーがそれらをやってくれます。結果として、同じ動作を維持しつつ、より簡潔なプロパティ定義になります。

C# 14 のバージョンがどれほどすっきりしているかに注目してください。

必要であれば、get アクセサーでも field を使えます。たとえば、遅延初期化を実装するなら次のように書けます。

public string Name 
{
    get => field ??= "Unknown";
    set => field = value;
}

このケースでは、最初に Name にアクセスしたとき、まだ設定されていなければ、ゲッターはバッキングフィールドにデフォルト "Unknown" を割り当てて返します。以降の get や set は同じ field を使います。この機能がなければ、同じ動作を実現するために private フィールドとゲッター内の追加コードが必要でした。

コンパイラーは field キーワードをどう扱うのか?

プロパティ アクセサー内で field を使うと、コンパイラーはそのプロパティの隠しバッキングフィールドを静かに生成します (自動実装プロパティに対する処理に非常に似ています)。このフィールドはソースコードでは決して見えませんが、コンパイラーが内部名 (たとえば <Hours>k__BackingField のようなもの) を付け、プロパティの値を保持するために使います。内部では次のようなことが起きています。

要するに、コンパイラーが private なバッキングフィールドを生成し、プロパティ アクセサーがそれを使うように配線してくれます。あなたは少ないコードでフルプロパティの機能を得られます。実装上、プロパティは依然として真の自動プロパティのままで、ロジックを差し込むためのフックを得たというだけです。

field の構文と使用ルール

field キーワードを使うときは、次のルールと制約を念頭に置いてください。

簡単に言えば、プロパティのアクセサーの中ではそのプロパティの隠しストレージを参照するために field を使い、それ以外では使わないでください。プロパティの外側のものについては、通常の C# のスコープ規則に従います。

命名の衝突への対処 (field という独自の変数を持っている場合)

以前の C# では field は予約語ではなかったため、(まれですが) 一部のコードが “field” を変数名やフィールド名として使っていた可能性があります。アクセサーに文脈依存キーワード field が導入されたことで、そうしたコードはあいまいになったり壊れたりする可能性があります。言語設計はこれを考慮しています。

private int field = 10; // a field unfortunately named "field" 
public int Example
{
    get { return @field; } // use @field to return the actual field 
    set { @field = value; } // or this.field = value; either works 
}

参考資料

  1. field – Field backed property declarations
  2. C# Feature Proposal Notes – field keyword in properties”
  3. What’s new in C# 14

Comments

Sign in with GitHub to comment. Reactions and replies thread back to the comments repo.

< 戻る