次の方法で共有


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

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

まとめ

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

目的

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

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

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

用語集

  • Auto プロパティ:「自動的に実装されるプロパティ」の略 (§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 にデータバインドされる可能性があり、変更の追跡や他の動作のトリガーを引き起こす可能性があります。 次のコードでは、HasPendingChangestrue に設定せずに、IsActive の既定値を初期化する必要があります。

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 (RuntimeHelpers.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アクセサーが NULL 安全性を維持することをdefault意味します。

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

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

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

  • フィールドに [field: MaybeNull]AllowNullNotNullDisallowNullなどの null 許容属性がある場合、フィールドの null 許容注釈はプロパティの null 許容注釈と同じです。
    • これは、ユーザーがフィールドに NULL 値の許容属性を適用し始めると、何も推測する必要がなくなり、NULL 値の許容がユーザーの発言どおりになることが望まれるためです
  • 包含プロパティに無意識または注釈付きの NULL 値の許容がある場合、バッキング フィールドにはプロパティと同じ NULL 値の許容が設定されます。
  • 包含プロパティに注釈なしの NULL 値の許容がある場合 (例: string または T)、または [NotNull] 属性があり、プロパティが NULL 回復性である場合、バッキング フィールドに NULL 値の許容の注釈が付けられます
  • 包含プロパティに注釈なしの NULL 値の許容がある場合 (例: string または T)、または [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; によって与えられるフロー状態と同じです。

セッター内の明示的または暗黙的な各 '返却' で、バッキング フィールドのフロー状態注釈と NULL 値の許容属性と互換性がない場合、警告が報告されます。

Remarks

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

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

代替案が検討されました。Null 許容の代替方法 セクションを参照してください。

nameof

field がキーワードである場所では、nameof(nint) のように、nameof(field) はコンパイルに失敗します (LDM の判断)。 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 の判断) のプライマリ式 (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、非参照値のプロパティです。自動プロパティには get アクセサーが必要で、オプションで set アクセサーを持つこともできます。次のいずれかまたは両方:

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

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

隠しバッキング フィールドにアクセスできない場合は、自動的に実装されるプロパティ アクセサーを介してのみ読み取りおよび書き込みを行うことができます(包含型内でも)。バッキング フィールドは、すべてのアクセサー内およびプロパティ式本文内で、field キーワードを使用して直接参照できます。フィールドは名前が付いていないため、nameof 式では使用できません。

自動プロパティに set アクセサーがなく、セミコロンのみの get アクセサーががある場合、バッキング フィールドが考慮されますreadonly (§15.5.3)。 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 許容の代替方法

NULL 値の許容」セクションで概説されている 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 でないと想定されます。
  • また、AllowNull が暗黙的に存在するかのように、field キーワード (または一般的なフィールド) に対する [field: MaybeNull] の動作を調整して、変数にも null を書き込むこともできます。

回答した LDM の質問

キーワードの構文の位置

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

  1. 常時
  2. 一次式のみ
  3. never

最初の 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 値の許容を受け入れるべきですか? 「NULL 値の許容」セクションと、内部の未解決の質問を参照してください。

答え

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

プロパティ初期化子の 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

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

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?
}

答え

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

[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 プロパティで認識され、自動アクセサーは合成されたバッキング フィールドを参照します。 インスタンス プロパティの場合、インスタンス フィールドがサポートされていないことを示すエラーが報告されます。

答え

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