次の方法で共有


fieldプロパティ内のキーワード

この記事は機能仕様です。 仕様は、機能の設計ドキュメントとして機能します。 これには、提案された仕様の変更と、機能の設計と開発時に必要な情報が含まれます。 これらの記事は、提案された仕様の変更が最終決定され、現在の ECMA 仕様に組み込まれるまで公開されます。

機能の仕様と完成した実装の間には、いくつかの違いがある可能性があります。 これらの違いは、関連 する言語設計会議 (LDM) のノートでキャプチャされます。

仕様に関する記事では、C# 言語標準に機能の仕様を採用するプロセスの詳細を確認できます。

チャンピオンの課題: https://github.com/dotnet/csharplang/issues/8635

まとめ

新しいコンテキスト キーワード field を使用して、自動的に生成されたバッキング フィールドを参照できるように、すべてのプロパティを拡張します。 プロパティには、ボディなしのアクセサーの他に、ボディ付きのアクセサーを含めることもできます。

目的

自動プロパティでは、バッキング フィールドを直接設定または取得することのみが可能で、アクセサーにアクセス修飾子を配置することによってのみ制御できます。 一方または両方のアクセサーでの挙動を追加で制御する必要がある場合がありますが、このとき、ユーザーはバッキングフィールドを宣言する手間に直面することになります。 その後、バッキング フィールド名はプロパティと同期する必要があり、バッキング フィールドのスコープはクラス全体に設定されるため、クラス内からアクセサーが誤ってバイパスされる可能性があります。

いくつかの一般的なシナリオがあります。 ゲッター内には、プロパティが指定されていない場合は遅延初期化または既定値があります。 セッター内には、値の有効性を確保するための制約を適用するか、INotifyPropertyChanged.PropertyChanged イベントを発生させるなどの更新を検出して伝達します。

このような場合は、常にインスタンス フィールドを作成し、プロパティ全体を自分で記述する必要があります。 これにより、かなりの量のコードが追加されるだけでなく、アクセサーの本体でのみ使用できるようにすることが望ましい場合であっても、バッキング フィールドが型の範囲の残りの部分に漏れてしまいます。

用語集

  • 自動プロパティ: "自動的に実装されるプロパティ" の省略形 (§15.7.4)。 自動プロパティのアクセサーには本文がありません。 実装とバッキング ストレージはどちらもコンパイラによって提供されます。 自動プロパティには、{ get; }{ get; set; }、または { get; init; } があります。

  • オート アクセサー: "自動的に実装されるアクセサー" の省略形。これは、本文のないアクセサーです。 実装とバッキング ストレージはどちらもコンパイラによって提供されます。 get;set;、および init; は自動アクセサーです。

  • フル アクセサー: 本文を持つアクセサーです。 実装はコンパイラによって提供されませんが、バッキング ストレージは引き続き (例 set => field = value; のように) 存在する可能性があります。

  • フィールドに基づくプロパティ: アクセサー本体内で field キーワードを使用するプロパティ、または自動プロパティです。

  • バッキング フィールド: これは、プロパティのアクセサーの field キーワードによって示される変数です。これは、自動的に実装されるアクセサー (get;set;、または init;) でも暗黙的に読み取りまたは書き込まれます。

詳細な設計

init アクセサーを持つプロパティの場合、set に以下で適用されるすべてのものが、代わりに init アクセサーに適用されます。

構文には次の 2 つの変更があります。

  1. 新しいコンテキストキーワード fieldが導入されました。このキーワードは、プロパティアクセサーの本体内で用いられ、プロパティ宣言のバッキングフィールドにアクセスできます (LDM の決定)。

  2. プロパティで自動アクセサーと完全アクセサーを組み合わせて使用できるようになりました (LDM 決定)。 「自動プロパティ」は、アクセサーに本文がないプロパティを指します。 以下の例はいずれも自動プロパティと見なされることはありません。

例:

{ get; set => Set(ref field, value); }
{ get => field ?? parent.AmbientValue; set; }

両方のアクセサーは、fieldを利用して、いずれかまたは両方が完全なアクセサーとして機能する場合があります。

{ get => field; set => field = value; }
{ get => field; set => throw new InvalidOperationException(); }
{ get => overriddenValue; set => field = value; }
{
    get;
    set
    {
        if (field == value) return;
        field = value;
        OnXyzChanged();
    }
}

式形式のプロパティと、getアクセサーfieldのみを持つプロパティでも使用できます。

public string LazilyComputed => field ??= Compute();
public string LazilyComputed { get => field ??= Compute(); }

set-only プロパティでは、fieldを使用することもあります。

{
    set
    {
        if (field == value) return;
        field = value;
        OnXyzChanged(new XyzEventArgs(value));
    }
}

重大な変更

プロパティ アクセサー本体内に field コンテキスト キーワードが存在することは、破壊的変更になる可能性があります。

field はキーワードであり、識別子ではないため、通常のキーワードエスケープ ルート (@field) を使用して識別子によってのみ "シャドウ" できます。 プロパティ アクセサー本体内で宣言された field という名前のすべての識別子は、最初の @ を追加することで、14 より前の C# バージョンからアップグレードするときに中断から保護できます。

プロパティ アクセサーで field という名前の変数が宣言されている場合は、エラーが報告されます。

言語バージョン 14 以降では、 プライマリ式field がバッキング フィールドを参照しているが、以前の言語バージョンでは別のシンボルを参照していた場合、警告が報告されます。

フィールドに特化した属性

自動プロパティと同様に、アクセサーの 1 つでバッキング フィールドを使用するプロパティでもフィールド ターゲット属性を使用できます。

[field: Xyz]
public string Name => field ??= Compute();

[field: Xyz]
public string Name { get => field; set => field = value; }

アクセサーがバッキング フィールドを使用しない限り、フィールド ターゲット属性は無効なままです。

// ❌ Error, will not compile
[field: Xyz]
public string Name => Compute();

プロパティの初期化子

初期化子を持つプロパティでは field を使用できます。 バッキングフィールドはセッターが呼び出されるのではなく、直接初期化されます (LDM 決定)。

初期化子のセッターの呼び出しはオプションではありません。初期化子は基本コンストラクターを呼び出す前に処理され、基本コンストラクターが呼び出される前にインスタンス メソッドを呼び出すことは不正となります。 これは、構造体の既定の初期化/確定割り当てにも重要です。

これにより、初期化を柔軟に制御できます。 セッターを呼び出さずに初期化する場合はプロパティ初期化子を使用します。 セッターを呼び出して初期化する場合は、コンストラクターでプロパティに初期値を割り当てます。

これが役に立つ例を次に示します。 field キーワードは、INotifyPropertyChanged パターンに優れたソリューションがあるため、ビュー モデルで多くの用途が見つかると考えています。 ビュー モデル のプロパティ セッターは、UI にデータバインドされる可能性があり、変更の追跡や他の動作のトリガーを引き起こす可能性があります。 次のコードでは、IsActiveHasPendingChanges に設定せずに、true の既定値を初期化する必要があります。

class SomeViewModel
{
    public bool HasPendingChanges { get; private set; }

    public bool IsActive { get; set => Set(ref field, value); } = true;

    private bool Set<T>(ref T ___location, T value)
    {
        if (EqualityComparer<T>.Default.Equals(___location, value))
            return false;

        ___location = value;
        HasPendingChanges = true;
        return true;
    }
}

プロパティ初期化子とコンストラクターからの割り当ての動作のこの違いは、以前のバージョンの言語の仮想自動プロパティでも確認できます。

using System;

// Nothing is printed; the property initializer is not
// equivalent to `this.IsActive = true`.
_ = new Derived();

class Base
{
    public virtual bool IsActive { get; set; } = true;
}

class Derived : Base
{
    public override bool IsActive
    {
        get => base.IsActive;
        set
        {
            base.IsActive = value;
            Console.WriteLine("This will not be reached");
        }
    }
}

コンストラクターの割り当て

自動プロパティと同様に、コンストラクターの割り当ては (仮想的な可能性がある) セッターが存在する場合にセッターを呼び出し、セッターがない場合はバッキング フィールドに直接割り当てることにフォールバックします。

class C
{
    public C()
    {
        P1 = 1; // Assigns P1's backing field directly
        P2 = 2; // Assigns P2's backing field directly
        P3 = 3; // Calls P3's setter
        P4 = 4; // Calls P4's setter
    }

    public int P1 => field;
    public int P2 { get => field; }
    public int P4 { get => field; set => field = value; }
    public int P3 { get => field; set; }
}

構造体での明確な割り当て

コンストラクターで参照することはできませんが、 field キーワードで示されるバッキング フィールドは、他の構造体フィールドと同じ条件 (LDM 決定 1LDM 決定 2) の下で、既定の初期化と既定で無効にされる警告の対象となります。

次に例を示します (これらの診断は既定ではサイレントです)。

public struct S
{
    public S()
    {
        // CS9020 The 'this' object is read before all of its fields have been assigned, causing preceding implicit
        // assignments of 'default' to non-explicitly assigned fields.
        _ = P1;
    }

    public int P1 { get => field; }
}
public struct S
{
    public S()
    {
        // CS9020 The 'this' object is read before all of its fields have been assigned, causing preceding implicit
        // assignments of 'default' to non-explicitly assigned fields.
        P2 = 5;
    }

    public int P2 { get => field; set => field = value; }
}

参照を返すプロパティ

自動プロパティと同様に、field キーワードは参照を返すプロパティでは使用できません。 参照を返すプロパティは、set アクセサーを持つことはできません。set アクセサーがないと、get アクセサーとプロパティ初期化子だけがバッキング フィールドにアクセスできます。 このためのユース ケースはありません。現在は、参照を返すプロパティを自動プロパティとして書き込めるタイミングではありません。

NULL 値の許容

Null 許容参照型機能の原則は、C# の既存の慣用的なコーディング パターンを理解し、それらのパターンに関する形式的な操作をできるだけ少なくすることです。 field キーワードの提案により、単純で慣用的なパターンが可能になり、遅延初期化プロパティなど、広く求められているシナリオに対処できます。 Null 許容参照型は、これらの新しいコーディング パターンとうまく統合されることが重要です。

目標:

  • field キーワード機能のさまざまな使用パターンに対して、適切なレベルの null 安全性を確保する必要があります。

  • field キーワードを使用するパターンは、常に言語の一部であったかのように感じる必要があります。 field キーワード機能に完全に慣用的なコードで Null 許容参照型を有効にするために、ユーザーが面倒な手順を踏まないように注意してください。

主なシナリオの 1 つは、遅延初期化プロパティです。

public class C
{
    public C() { } // It would be undesirable to warn about 'Prop' being uninitialized here

    string Prop => field ??= GetPropValue();
}

次の null 許容ルールは、field キーワードを使用するプロパティだけでなく、既存の自動プロパティにも適用されます。

バッキング フィールドの NULL 値の許容

新しい用語の定義については、 用語集 を参照してください。

バッキング フィールドの型はプロパティと同じです。 ただし、許容注釈はプロパティと異なる場合があります。 この NULL 許容注釈を決定するために、NULL 回復性の概念を紹介します。 Null 回復性は、直感的に、フィールドに型のget値が含まれている場合でも、プロパティの default アクセサーが null 安全性を保持することを意味します。

フィールド バック プロパティは、そのアクセサの特別な NULL 許容分析を実行することによって、NULL 回復性があるかどうかがget決定されます。

  • この分析の目的上、 field は一時的に 注釈付きの null 値許容 (たとえば、 string?) を持っていると見なされます。 これにより、fieldの型に応じて、 アクセサーで nullまたはgetの初期状態になる可能性があります。
  • 次に、ゲッターの NULL 許容分析で NULL許容警告が生成されない場合、プロパティは NULL 回復性を持ちます。 それ以外の場合は、NULL 回復性がありません。
  • プロパティに get アクセサーがない場合、そのプロパティは (空虚に) NULL 回復性を持ちます。
  • get アクセサーが自動実装されている場合、プロパティには null 回復性がありません。

バッキング フィールドの null 値の許容は次のように決定されます。

  • フィールドに [field: MaybeNull]AllowNullNotNullDisallowNullなどの null 許容属性がある場合、フィールドの null 許容注釈はプロパティの null 許容注釈と同じです。
    • これは、ユーザーがフィールドに null 許容属性を適用し始めるとき、何も推測する必要がなくなり、null 許容を ユーザーの言った内容にしたいだけであるためです。
  • 包含プロパティに無効または注釈付きの null 許容がある場合、バッキング フィールドはプロパティと同じ null 許容値を持つ。
  • 包含プロパティに 注釈されていない null 可能性 ( stringT など) がある場合、または [NotNull] 属性があり、プロパティが null 耐性である場合、バッキング フィールドには 注釈された null 可能性があります。
  • 包含プロパティに 注釈のない null 許容 ( stringTなど) がある場合、または [NotNull] 属性があり、プロパティに null 回復性がない場合、バッキング フィールドには 注釈付き null 値の許容がありません。

コンストラクターの分析

現在、自動プロパティは null 許容コンストラクター分析の通常のフィールドとよく似た方法で扱われます。 この処理は、すべてのフィールドに基づくプロパティ をそのバッキングフィールドへのプロキシとして扱うことで、フィールドに基づくプロパティ まで拡張されます。

これを実現するために、前の 提案されたアプローチ から次の仕様言語を更新します。

コンストラクター内の明示的または暗黙的な「戻り値」ごとに、フロー状態が注釈および null 許容属性と互換性のない各メンバーに対して警告を表示します。 メンバーがフィールドに基づくプロパティの場合、バッキング フィールドの NULL 許容注釈がこのチェックに使用されます。 それ以外の場合は、メンバー自体の NULL 許容注釈が使用されます。 これに対する適切なプロキシは、戻りポイントでメンバーを自身に割り当てると NULL 値の許容の警告が生成された場合、戻りポイントで NULL 値の許容の警告が生成されます。

これは本質的に制約付き相互解析であることに注意してください。 コンストラクターを分析するには、 field コンテキスト キーワードを使用し、 注釈のない null 値の許容を持つ、同じ型のすべての適用可能な get アクセサーに対してバインディングと "null 回復性" 分析を行う必要があると予測しています。 ゲッター本体は通常あまり複雑ではなく、型内のコンストラクターの数に関係なく、「null 回復性」分析を 1 回だけ実行する必要があるため、これが法外に高価になることはないと推測しています。

セッター分析

わかりやすくするために、「setter」および「set アクセサー」という用語を使って、set アクセサーまたは init アクセサーを指します。

フィールドに基づくプロパティのセッターが実際にバッキング フィールドを初期化することを確認する必要があります。

class C
{
    string Prop
    {
        get => field;

        // getter is not null-resilient, so `field` is not-annotated.
        // We should warn here that `field` may be null when exiting.
        set { }
    }

    public C()
    {
        Prop = "a"; // ok
    }

    public static void Main()
    {
        new C().Prop.ToString(); // NRE at runtime
    }
}

フィールドバックプロパティのセッター内のバッキングフィールドの初期フロー状態は、次のように決定されます。

  • プロパティに初期化子がある場合、初期フロー状態は初期化子にアクセスした後のプロパティのフロー状態と同じです。
  • それ以外の場合、初期フロー状態は field = default; によって与えられるフロー状態と同じです。

セッターの明示的または暗黙的な 'return' ごとに、 バッキング フィールド のフロー状態が注釈および null 許容属性と互換性がない場合、警告が報告されます。

注釈

この定式化は、コンストラクターの通常のフィールドと意図的によく似せています。 基本的に、プロパティ アクセサーのみが実際にバッキング フィールドを参照できるため、セッターはバッキング フィールドの「ミニ コンストラクター」として扱われます。

通常のフィールドと同様に、プロパティが設定されたためにコンストラクターで初期化されたことは通常わかっていますが、必ずしもそうとは限りません。 Prop != null が true であったブランチ内に戻るだけで、コンストラクターの分析にも十分です。これは、追跡されていないメカニズムがプロパティの設定に使用されている可能性があることを理解しているためです。

代替案が検討されました。 「Nullability alternatives 」セクションを参照してください。

nameof

field がキーワードである場所では、nameof(field) のように、 はコンパイルに失敗します (nameof(nint))。 nameof(value) とは違います。これは、プロパティ セッターが .NET Core ライブラリの場合と同様に ArgumentException をスローするときに使用します。 これに対し、nameof(field) には想定されるユース ケースはありません。

上書き

プロパティをオーバーライドする場合、fieldを使用できます。 このような field の使用法は、オーバーライドするプロパティのためのバッキング フィールドを参照しています。これは、ベース プロパティのバッキングフィールドがある場合、別のバッキング フィールドです。 これによりカプセル化が中断されるため、基本プロパティのバッキング フィールドをオーバーライドするクラスに公開するための ABI はありません。

自動プロパティと同様に、 field キーワードを使用し、基本プロパティをオーバーライドするプロパティは、すべてのアクセサーをオーバーライドする必要があります (LDM 決定)。

キャプチャ

field はローカル関数とラムダでキャプチャでき、他の参照がない場合でも、ローカル関数とラムダ内からの field への参照が許可されます (LDM 決定 1LDM 決定 2)。

public class C
{
    public static int P
    {
        get
        {
            Func<int> f = static () => field;
            return f();
        }
    }
}

フィールドの使用に関する警告

アクセサーで field キーワードを使用すると、割り当てられていないフィールドまたは未読フィールドのコンパイラの既存の分析にそのフィールドが含まれます。

  • CS0414: プロパティ 'Xyz' のバッキング フィールドが割り当てられていますが、その値が使用されることはありません
  • CS0649: プロパティ 'Xyz' のバッキング フィールドが割り当てられることはなく、常に既定値になります

仕様の変更

構文

言語バージョン 14 以上でコンパイルする場合、次の場所 (field 決定) でプライマリ式 (LDM 決定) として使用する場合、はキーワードと見なされます。

  • プロパティ内の getset、および init アクセサのメソッド本体内 (インデクサ内ではない)
  • これらのアクセサーに適用される属性
  • 入れ子になったラムダ式とローカル関数、およびそれらのアクセサー内での LINQ 式

言語バージョン 12 以下でコンパイルする場合を含め、他のすべてのケースでは、field は識別子と見なされます。

primary_no_array_creation_expression
    : literal
+   | 'field'
    | interpolated_string_expression
    | ...
    ;

プロパティ

§15.7.1プロパティ - 全般

property_initializer、自動的に実装されるプロパティと、出力されるバッキング フィールドを持つプロパティに対してのみ指定できます。property_initializerにより、式によって指定された値を使用して、このようなプロパティの基になるフィールドが初期化されます

§15.7.4プロパティを自動的に実装する

自動的に実装されるプロパティ (または短い場合は自動プロパティ) は、 セミコロンのみのアクセサー本体を持つ非抽象プロパティ、非 extern プロパティ、非 ref 値プロパティです。自動プロパティには get アクセサーがあり、必要に応じて set アクセサーを持つことができます。次のいずれかまたは両方:

  1. セミコロンのみの本体を持つアクセサー
  2. field 式本文内の コンテキスト キーワードの使用

プロパティが自動的に実装されるプロパティとして指定されている場合、非表示の 名前のない バッキング フィールドがプロパティに対して自動的に使用でき 、アクセサーはそのバッキング フィールドの読み取りと書き込みを行うために実装されます自動プロパティの場合、読み取りを行うためにセミコロン専用の get アクセサーが実装され、そのバッキング フィールドに書き込むセミコロン専用setアクセサーが実装されます。

非表示のバッキング フィールドにはアクセスできません。読み取りおよび書き込みは、自動的に実装されたプロパティ アクセサーを介してのみ、包含型内であっても行うことができます。field

自動プロパティに< c0 >set アクセサーがなく、< c1 >get アクセサーがセミコロンのみの場合、バッキング フィールドは< c2 />と見なされます (< c3 >§15.5.3< /c3 >)。 readonly フィールドと同様に、読み取り専用の自動プロパティ (set アクセサーまたは init アクセサーを使用しない) も、外側のクラスのコンストラクターの本体に割り当てることができます。 このような割り当ては、プロパティの 読み取り専用 バッキング フィールドに直接割り当てられます。

自動プロパティは、set アクセサーがない状態で、セミコロンのみの get アクセサーを持つことはできません。

自動プロパティには必要に応じて property_initializerがあり、バッキング フィールドに variable_initializer として直接適用されます (§17.7)。

次のような例です。

// No 'field' symbol in scope.
public class Point
{
    public int X { get; set; }
    public int Y { get; set; }
}

これは次の宣言と同等です。

// No 'field' symbol in scope.
public class Point
{
    public int X { get { return field; } set { field = value; } }
    public int Y { get { return field; } set { field = value; } }
}

これは次に相当します。

// No 'field' symbol in scope.
public class Point
{
    private int __x;
    private int __y;
    public int X { get { return __x; } set { __x = value; } }
    public int Y { get { return __y; } set { __y = value; } }
}

次のような例です。

// No 'field' symbol in scope.
public class LazyInit
{
    public string Value => field ??= ComputeValue();
    private static string ComputeValue() { /*...*/ }
}

これは次の宣言と同等です。

// No 'field' symbol in scope.
public class Point
{
    private string __value;
    public string Value { get { return __value ??= ComputeValue(); } }
    private static string ComputeValue() { /*...*/ }
}

代替

NULL 許容の代替方法

Nullability」セクションで概説されている null 回復性アプローチに加えて、作業グループは LDM の考慮事項に対して次の代替手段を提案しました。

何もしない

ここでは特別な動作をまったく導入できませんでした。 事実上、次のようになります。

  • フィールドベースのプロパティは、現在の自動プロパティと同じように扱います。ただし、必須としてマークされている場合を除いて、コンストラクターで初期化する必要があります。
  • プロパティ アクセサーを分析する場合、フィールド変数の特別な扱いはありません。 これは単に、プロパティと同じ型と null 許容を持つ変数です。

これにより、「遅延プロパティ」シナリオで迷惑警告が発生します。その場合、ユーザーはコンストラクターの警告を無効化するために、null! またはそれに類似したものを割り当てる必要があるでしょう。
考慮できる代替方法の 1 つは、NULL 許容コンストラクター分析に field キーワードを使用してプロパティを完全に無視することです。 その場合、使用している初期化パターンに関係なく、ユーザーが何かを初期化する必要があるという警告はなく、ユーザーにとって迷惑になることもありません。

.NET 9 のプレビュー LangVersion の下で field キーワード機能を出荷する予定であるため、.NET 10 ではその機能の null 許容動作を変更できる可能性があると考えています。 したがって、このような"低コスト"ソリューションを短期的に採用し、長期的にはより複雑なソリューションの 1 つに成長させることを検討できます。

field の対象となる NULL 値の許容属性

次の既定値を導入することにより、手続間解析をまったく必要とせずに、妥当なレベルの null 安全性を実現することができます。

  1. field 変数には、プロパティと同じ null 許容注釈が常に含まれます。
  2. null 許容属性 [field: MaybeNull, AllowNull] などを使用して、バッキング フィールドの null 許容をカスタマイズできます。
  3. フィールドにサポートされたプロパティは、フィールドの NULL 許容注釈と属性に基づいて、コンストラクターで初期化がチェックされます。
  4. フィールドに基づくプロパティのセッターはコンストラクターと同様に field の初期化をチェックします。

つまり、「ちょっとした怠け者シナリオ」は次のようになります。

class C
{
    public C() { } // no need to warn about initializing C.Prop, as the backing field is marked nullable using attributes.

    [field: AllowNull, MaybeNull]
    public string Prop => field ??= GetPropValue();
}

私たちがここでヌル許容属性を使用しない一因は、それらが実際にはシグネチャの入力と出力の説明に重点を置いているためです。 有効期間の長い変数の null 許容を記述するために使用するのは面倒です。

  • 実際には、フィールドを null 許容変数として "合理的に" 動作させるために [field: MaybeNull, AllowNull] が必要です。これにより、null の可能性あり初期フロー状態が得られ、null 値が書き込まれる可能性があります。 比較的一般的な「ちょっとした怠け者」のシナリオでは、ユーザーにこれを実行するよう求めるのは面倒に感じます。
  • このアプローチを追求した場合は、[field: AllowNull] を使用するときに警告を追加することを検討し、MaybeNull も追加することをお勧めします。 これは、AllowNull 自体が null 許容変数からユーザーが必要とするものを行わないためです。フィールドへの書き込みがまだ見られない場合、フィールドは最初は null でないと想定されます。
  • また、[field: MaybeNull] が暗黙的に存在するかのように、field キーワード (または一般的なフィールド) に対する AllowNull の動作を調整して、変数にも null を書き込むこともできます。

回答した LDM の質問

キーワードの構文の位置

fieldvalue が合成されたバッキング フィールドまたは暗黙的なセッター パラメーターにバインドできるアクセサーでは、どの構文の場所で識別子をキーワードと見なす必要がありますか?

  1. 常時
  2. プライマリ式 のみ
  3. 一度もない

最初の 2 つのケースは破壊的変更です。

識別子が 常に キーワードと見なされる場合、これは次のような重大な変更です。

class MyClass
{
    private int field;
    public int P => this.field; // error: expected identifier

    private int value;
    public int Q
    {
        set { this.value = value; } // error: expected identifier
    }
}

識別子がプライマリ式としてのみ使用される場合にキーワードである場合、破壊的変更は小さくなります。 最も一般的な中断は、「field」という名前の既存メンバーを無修飾で使用することです。

class MyClass
{
    private int field;
    public int P => field; // binds to synthesized backing field rather than 'this.field'
}

また、入れ子になった関数で field または value が再宣言されると、中断が発生します。 これは、value に対する唯一の中断である可能性があります。

class MyClass
{
    private IEnumerable<string> _fields;
    public bool HasNotNullField
    {
        get => _fields.Any(field => field is { }); // 'field' binds to synthesized backing field
    }
    public IEnumerable<string> Fields
    {
        get { return _fields; }
        set { _fields = value.Where(value => Filter(value)); } // 'value' binds to setter parameter
    }
}

識別子がキーワードと見な されない 場合、識別子は、他のメンバーにバインドされていない場合にのみ、合成されたバッキング フィールドまたは暗黙的なパラメーターにバインドされます。 この場合、破壊的変更はありません。

答え

field は、 プライマリ式 としてのみ使用される場合、適切なアクセサーのキーワードです。 value はキーワードとは見なされません。

{ set; } に類似したシナリオ

{ set; } は現在許可されていません。これは理にかなっています。これが作成するフィールドを読み取ることは決してできません。 現在、新しい方法が現れており、セッターが { set; }{ set => field = value; }に拡張するなど、読み取られることのないバッキングフィールドを導入する状況に陥ることがあります。

これらのシナリオのうち、コンパイルを許可する必要があるシナリオはどれですか? "フィールドが読み取られることはない" という警告が、手動で宣言されたフィールドと同じように適用されると見なされます。

  1. { set; } - 今日は許可されていない、継続して許可しない
  2. { set => field = value; }
  3. { get => unrelated; set => field = value; }
  4. { get => unrelated; set; }
  5. {
        set
        {
            if (field == value) return;
            field = value;
            SendEvent(nameof(Prop), value);
        }
    }
    
  6. {
        get => unrelated;
        set
        {
            if (field == value) return;
            field = value;
            SendEvent(nameof(Prop), value);
        }
    }
    

答え

現在、自動プロパティで既に禁止されている本文なしの set; だけを禁止します。

イベント アクセサーの field

イベント アクセサーで field をキーワードにすべきか、そしてコンパイラがバックフィールドを生成すべきでしょうか?

class MyClass
{
    public event EventHandler E
    {
        add { field += value; }
        remove { field -= value; }
    }
}

推奨事項: field はイベント アクセサー内のキーワード ではなく 、バッキング フィールドは生成されません。

答え

推奨を受け入れました。 field はイベント アクセサー内のキーワード ではなく 、バッキング フィールドは生成されません。

field の NULL 値の許容

提案された field の NULL 値の許容を受け入れるべきですか? 「Nullability」セクションと、その中の未解決の質問を参照してください。

答え

一般的な提案が採用されます。 特定の動作には、さらにレビューが必要です。

プロパティ初期化子の field

プロパティ初期化子で field がキーワードとなり、バッキングフィールドにバインドされるべきか?

class A
{
    const int field = -1;

    object P1 { get; } = field; // bind to const (ok) or backing field (error)?
}

初期化子のバッキング フィールドを参照するための便利なシナリオはありますか?

class B
{
    object P2 { get; } = (field = 2);        // error: initializer cannot reference instance member
    static object P3 { get; } = (field = 3); // ok, but useful?
}

上記の例では、バッキング フィールドにバインドすると、"初期化子は非静的フィールドを参照できません" というエラーが発生します。

答え

以前のバージョンの C# と同様に初期化子をバインドします。 バッキング フィールドをスコープに配置することも、field という名前の他のメンバーを参照することもありません。

部分プロパティとのインタラクション

初期化子

部分プロパティが fieldを使用する場合、初期化子を持つことが許可される必要がある部分はどれですか?

partial class C
{
    public partial int Prop { get; set; } = 1;
    public partial int Prop { get => field; set => field = value; } = 2;
}
  • 両方の部分に初期化子がある場合にエラーが発生する必要があるようです。
  • 定義または実装部分のいずれかが field の初期値を設定する必要があるユース ケースを考えることができます。
  • 定義部分で初期化子を許可すると、プログラムを有効にするために実装者が field を使用することが実質的に強制されているようです。 これについては問題ありませんか?
  • 同じ型のバッキング フィールドが実装で必要な場合は常に、ジェネレーターで field を使用するのが一般的であると考えています。 これは、ジェネレーターが多くの場合、ユーザーがプロパティ定義部分で [field: ...] ターゲット属性を使用できるようにする必要があるためです。 field キーワードを使用すると、このような属性を生成されたフィールドに転送し、プロパティに対する警告を抑制する手間をジェネレーターの実装者から省くことができます。 これらの同じジェネレーターでは、ユーザーがフィールドの初期値を指定できるようにする必要がある可能性もあります。

推奨事項: 実装部分で field を使用する場合は、部分プロパティのいずれかの部分で初期化子を許可します。 両方の部分に初期化子がある場合はエラーを報告します。

答え

推奨事項は受け入れ済みです。 プロパティの場所を宣言または実装する場合は、初期化子を使用できますが、同時に両方を使用することはできません。

自動アクセサー

最初に設計したように、部分プロパティの実装には、すべてのアクセサーの本体が必要です。 ただし、field キーワード機能の最近のイテレーションには、"自動アクセサー" という概念が含まれています。 部分プロパティ実装でこのようなアクセサーを使用できるようにする必要がありますか? それらが排他的に使用されている場合、定義宣言とは区別できません。

partial class C
{
    public partial int Prop0 { get; set; }
    public partial int Prop0 { get => field; set => field = value; } // this is equivalent to the two "semi-auto" forms below.

    public partial int Prop1 { get; set; }
    public partial int Prop1 { get => field; set; } // is this a valid implementation part?

    public partial int Prop2 { get; set; }
    public partial int Prop2 { get; set => field = value; } // what about this? will there be disagreement about which is the "best" style?

    public partial int Prop3 { get; }
    public partial int Prop3 { get => field; } // it will only be valid to use at most 1 auto-accessor, when a second accessor is manually implemented.

推奨事項: 部分的なプロパティ実装では自動アクセサーを禁止します。これは、使用可能になるタイミングに関する制限は、許可する利点よりも混乱を招くためです。

答え

少なくとも 1 つの実装アクセサーを手動で実装する必要がありますが、その他のアクセサーは自動的に実装できます。

読み取り専用フィールド

合成されたバッキング フィールドを 読み取り専用と見なす必要があるのはいつですか?

struct S
{
    readonly object P0 { get => field; } = "";         // ok
    object P1          { get => field ??= ""; }        // ok
    readonly object P2 { get => field ??= ""; }        // error: 'field' is readonly
    readonly object P3 { get; set { _ = field; } }     // ok
    readonly object P4 { get; set { field = value; } } // error: 'field' is readonly
}

バッキング フィールドが 読み取り専用と見なされると、メタデータに出力されるフィールドは initonlyマークされ、初期化子またはコンストラクター以外の field が変更された場合はエラーが報告されます。

推奨事項: 合成されたバッキング フィールドは、包含する型がで、プロパティまたは包含型がstruct宣言されている場合はreadonlyです。

答え

推奨事項は受け入れ済みです。

読み取り専用のコンテキストと set

set を使用するプロパティの readonly コンテキストで field アクセサーを許可する必要がありますか?

readonly struct S1
{
    readonly object _p1;
    object P1 { get => _p1; set { } }   // ok
    object P2 { get; set; }             // error: auto-prop in readonly struct must be readonly
    object P3 { get => field; set { } } // ok?
}

struct S2
{
    readonly object _p1;
    readonly object P1 { get => _p1; set { } }   // ok
    readonly object P2 { get; set; }             // error: auto-prop with set marked readonly
    readonly object P3 { get => field; set { } } // ok?
}

答え

このシナリオでは、set 構造体に readonly アクセサーを実装し、それを渡すかスローする場合があります。 これを許可する予定です。

[Conditional] コード

fieldが条件付きメソッドの省略された呼び出しでのみ使用されている場合、合成されたフィールドを生成する必要がありますか?

たとえば、DEBUG 以外のビルドでは、次のバッキング フィールドを生成する必要がありますか?

class C
{
    object P
    {
        get
        {
            Debug.Assert(field is null);
            return null;
        }
    }
}

参照については、 プライマリ コンストラクター パラメーター のフィールドも同様のケースで生成されます。 sharplab.io を参照してください。

推奨事項: バッキング フィールドは、fieldが条件付きメソッドの省略された呼び出しでのみ使用される場合に生成されます。

答え

Conditional コードは、nullability を変更する Debug.Assert など、非条件付きコードに影響を及ぼす場合があります。 field に似た影響がないのは奇妙なことです。 また、ほとんどのコードで出てくる可能性は低いので、簡単なことを行い、推奨事項を受け入れます。

インターフェイス プロパティと自動アクセサー

自動実装アクセサーが合成されたバッキング フィールドを参照する interface プロパティに対して、手動で実装されたアクセサーと自動実装アクセサーの組み合わせが認識されますか?

インスタンス プロパティの場合、インスタンス フィールドがサポートされていないことを示すエラーが報告されます。

interface I
{
           object P1 { get; set; }                           // ok: not an implementation
           object P2 { get => field; set { field = value; }} // error: instance field

           object P3 { get; set { } } // error: instance field
    static object P4 { get; set { } } // ok: equivalent to { get => field; set { } }
}

推奨事項: オートアクセサーは interface プロパティで認識され、自動アクセサーは合成されたバッキング フィールドを参照します。 インスタンス プロパティの場合、インスタンス フィールドがサポートされていないことを示すエラーが報告されます。

答え

エラーの原因であるインスタンス フィールド自体を標準化することは、クラスの部分プロパティと一致しており、その結果を気に入っています。 推奨事項は受け入れ済みです。