field
Schlüsselwort in Eigenschaften
Zusammenfassung
Erweitern Sie alle Eigenschaften so, dass sie mit dem neuen kontextuellen Schlüsselwort auf ein automatisch generiertes Hintergrundfeld verweisen können. field
. Eigenschaften können jetzt auch einen Accessor enthalten ohne einen Körper neben einem Accessor mit einen Körper.
Motivation
Auto-Eigenschaften erlauben nur das direkte Setzen oder Abrufen des Hintergrundfeldes, wobei eine gewisse Kontrolle nur durch die Platzierung von Zugriffsmodifikatoren an den Accessors möglich ist. Manchmal ist es notwendig, zusätzliche Kontrolle über die Vorgänge in einem oder beiden Accessoren zu haben, aber dies konfrontiert die Benutzer mit dem Overhead der Deklaration eines Backing-Feldes. Der Name des Sicherungsfeldes muss dann mit der Eigenschaft synchronisiert werden, und das Sicherungsfeld ist auf die gesamte Klasse beschränkt, was zu einer versehentlichen Umgehung der Zugriffsfunktionen innerhalb der Klasse führen kann.
Es gibt mehrere häufige Szenarien. Innerhalb des Getters gibt es eine faule Initialisierung oder Standardwerte, wenn die Eigenschaft nie angegeben wurde. Innerhalb des Setters wird eine Einschränkung angewendet, um die Gültigkeit eines Werts sicherzustellen oder Aktualisierungen zu erkennen und weiterzuleiten, z. B. durch Auslösen des INotifyPropertyChanged.PropertyChanged
-Ereignisses.
In diesen Fällen müssen Sie jetzt immer ein Instanzfeld erstellen und die gesamte Eigenschaft selbst schreiben. Dies fügt nicht nur eine Menge Code hinzu, sondern lässt auch das Backing-Feld in den übrigen Geltungsbereich des Typs durchsickern, obwohl es oft wünschenswert ist, dass es nur für die Körper der Accessoren verfügbar ist.
Glossar
Auto-Eigentum: Kurz für „automatisch implementierte Eigenschaft" (§15.7.4). Accessors auf eine Auto-Eigenschaft haben keinen Körper. Sowohl die Implementierung als auch die Speicherung der Daten werden vom Compiler bereitgestellt. Auto-Eigenschaften haben
{ get; }
,{ get; set; }
, oder{ get; init; }
.Auto accessor: Kurz für „automatisch implementierter Accessor“. Dies ist ein Accessor, der keinen Körper hat. Sowohl die Implementierung als auch die Speicherung der Daten werden vom Compiler bereitgestellt.
get;
,set;
undinit;
sind Auto-Accessoren.Vollständiger Accessor: Dies ist ein Accessor, der einen Körper hat. Die Implementierung wird vom Compiler nicht bereitgestellt, obwohl der Sicherungsspeicher möglicherweise noch vorhanden ist (wie im Beispiel
set => field = value;
).Feldgestütztes Eigentum: Dies ist entweder eine Eigenschaft, die die
field
Schlüsselwort innerhalb eines Accessor-Bodys oder einer Auto-Eigenschaft.Hinteres Feld: Dies ist die Variable, die mit dem Symbol
field
Schlüsselwort in den Accessors einer Eigenschaft, das auch implizit in automatisch implementierten Accessors gelesen oder geschrieben wird (get;
,set;
, oderinit;
).
Detailliertes Design
Für Immobilien mit einem init
Accessor, alles, was im Folgenden für set
würde stattdessen für die init
accessor.
Es gibt zwei Syntaxänderungen:
Es gibt ein neues kontextbezogenes Schlüsselwort,
field
, die innerhalb von Eigenschaftszugriffskörpern verwendet werden können, um auf ein Unterstützungsfeld für die Eigenschaftserklärung zuzugreifen (LDM-Entscheidung).Eigenschaften können nun Auto-Accessors mit Full-Accessors mischen und kombinieren (LDM-Entscheidung). „Auto-Eigenschaft“ bedeutet weiterhin eine Eigenschaft, deren Accessoren keine Körper haben. Keines der folgenden Beispiele wird als automatische Eigenschaften betrachtet.
Beispiele:
{ get; set => Set(ref field, value); }
{ get => field ?? parent.AmbientValue; set; }
Beide Accessoren können Voll-Accessoren sein, wobei entweder einer oder beide die 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();
}
}
Ausdrucksstarke Eigenschaften und Eigenschaften mit nur einem get
Accessor kann auch field
:
public string LazilyComputed => field ??= Compute();
public string LazilyComputed { get => field ??= Compute(); }
Nur-einstellen-Eigenschaften können auch verwende field
:
{
set
{
if (field == value) return;
field = value;
OnXyzChanged(new XyzEventArgs(value));
}
}
Bahnbrechende Änderungen
Das Vorhandensein des kontextbezogenen Schlüsselworts field
innerhalb von Eigenschaftsaccessortexten ist eine potenziell bahnbrechende Änderung.
Seit field
ein Schlüsselwort und kein Bezeichner ist, kann es nur durch einen Bezeichner „beschattet“ werden, indem man den normalen Weg der Schlüsselwortabtrennung benutzt: @field
. Alle Bezeichner mit dem Namen field
die innerhalb von Eigenschafts-Zugriffskörpern deklariert sind, können sich vor Brüchen schützen, wenn sie von C#-Versionen vor 14 aktualisiert werden, indem sie die anfängliche @
.
Wenn eine Variable mit dem Namen field
in einem Eigenschaftenaccessor deklariert wird, wird ein Fehler gemeldet.
In der Sprachversion 14 oder höher wird eine Warnung gemeldet, wenn ein Primärausdruckfield
auf das Backing-Feld verweist, aber in einer älteren Sprachversion auf ein anderes Symbol verwiesen hätte.
Auf das Feld ausgerichtete Attribute
Wie bei den Auto-Eigenschaften kann jede Eigenschaft, die ein Hintergrundfeld in einem ihrer Accessoren verwendet, feldspezifische Attribute verwenden:
[field: Xyz]
public string Name => field ??= Compute();
[field: Xyz]
public string Name { get => field; set => field = value; }
Ein feldbezogenes Attribut bleibt ungültig, es sei denn, ein Accessor verwendet ein Unterstützungsfeld:
// ❌ Error, will not compile
[field: Xyz]
public string Name => Compute();
Eigenschaftsinitialisierer
Eigenschaften mit Initialisierungen können mit field
. Das Backing-Feld wird direkt initialisiert und nicht der Setter aufgerufen (LDM-Entscheidung).
Der Aufruf eines Setters für einen Initialisierer ist keine Option; Initialisierer werden vor dem Aufruf von Basiskonstruktoren verarbeitet, und es ist illegal, eine Instanzmethode aufzurufen, bevor der Basiskonstruktor aufgerufen wird. Dies ist auch wichtig für die Standardinitialisierung/eindeutige Zuordnung von Strukturen.
Dies führt zu einer flexiblen Kontrolle über die Initialisierung. Wenn Sie die Initialisierung ohne Aufruf des Setters ausführen möchten, verwenden Sie einen Eigenschaftsinitialisierer. Wenn Sie durch den Aufruf des Setters initialisieren wollen, weisen Sie der Eigenschaft im Konstruktor einen Anfangswert zu.
Hier ist ein Beispiel dafür, wo dies nützlich ist. Wir glauben, dass die field
Schlüsselwort wird wegen der eleganten Lösung, die es für die INotifyPropertyChanged
Muster. Die Setter von View-Model-Eigenschaften sind wahrscheinlich datengebunden an die Benutzeroberfläche und führen wahrscheinlich zur Änderungsverfolgung oder lösen andere Verhaltensweisen aus. Der folgende Code muss den Standardwert von IsActive
initialisieren, ohne HasPendingChanges
auf true
festzulegen:
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;
}
}
Dieser Unterschied im Verhalten zwischen einem Eigenschaftsinitialisierer und der Zuweisung aus dem Konstruktor ist auch bei virtuellen Auto-Eigenschaften in früheren Versionen der Sprache zu beobachten:
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");
}
}
}
Zuweisung des Konstruktors
Wie bei den Auto-Eigenschaften wird bei der Zuweisung im Konstruktor der (möglicherweise virtuelle) Setter aufgerufen, sofern er vorhanden ist, und wenn es keinen Setter gibt, wird auf die direkte Zuweisung an das hintere Feld zurückgegriffen.
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; }
}
Eindeutige Zuweisung in Structs
Auch wenn sie im Konstruktor nicht referenziert werden können, können Backing-Felder, die mit dem field
unterliegen unter den gleichen Bedingungen wie alle anderen struct-Felder den Warnungen „default-initialization“ und „disabled-by-default“ (LDM-Entscheidung 1, LDM-Entscheidung 2).
Beispielsweise: (diese Diagnosen sind standardmäßig stumm):
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; }
}
Ref-Rückgabe-Eigenschaften
Wie bei den Auto-Eigenschaften ist die field
Schlüsselwort nicht für die Verwendung in Rückgabeeigenschaften zur Verfügung stehen wird. Ref-returning-Eigenschaften können keine Set-Accessoren haben, und ohne einen Set-Accessor wären der Get-Accessor und der Property-Initialisierer die einzigen Dinge, die auf das Backing-Feld zugreifen können. In Ermangelung von Anwendungsfällen ist es jetzt nicht an der Zeit, dass ref-returning properties als auto properties geschrieben werden können.
NULL-Zulässigkeit
Ein Grundsatz des Nullable Reference Types-Features war es, bestehende idiomatische Codierungsmuster in C# zu verstehen und so wenig Zeremonie wie möglich um diese Muster herum zu erfordern. Die field
Der Vorschlag für Schlüsselwörter ermöglicht einfache, idiomatische Muster für häufig nachgefragte Szenarien, wie z. B. träge initialisierte Eigenschaften. Es ist wichtig, dass die nullbaren Referenztypen gut mit diesen neuen Codierungsmustern zusammenpassen.
Ziele:
Für verschiedene Verwendungsmuster der
field
Schlüsselwortfunktion sollte ein angemessenes Maß an Nullsicherheit sichergestellt werden.Muster, die das schlüsselwort
field
verwenden, sollten sich so verhalten, als ob sie immer Teil der Sprache waren. Vermeiden Sie es, den Benutzer durch Reifen springen zu lassen, um nullbare Referenztypen in Code zu aktivieren, der vollkommen idiomatisch für diefield
Schlüsselwort-Feature.
Eines der Schlüsselszenarien ist die verzögerte Initialisierung von Eigenschaften:
public class C
{
public C() { } // It would be undesirable to warn about 'Prop' being uninitialized here
string Prop => field ??= GetPropValue();
}
Die folgenden Nullbarkeitsregeln gelten nicht nur für Eigenschaften, die das Schlüsselwort field
verwenden, sondern auch für vorhandene Autoeigenschaften.
Nullbarkeit der Unterstützungsfeld
Definitionen neuer Begriffe finden Sie unter Glossar.
Die Unterstützungsfeld hat den gleichen Typ wie die Eigenschaft. Die Annotation „nullable“ kann sich jedoch von der Eigenschaft unterscheiden. Um diese annullierbare Anmerkung zu bestimmen, führen wir das Konzept der Null-Resilienz. Null-Resilienz bedeutet intuitiv, dass die Eigenschaft get
wird die Nullsicherheit auch dann beibehalten, wenn das Feld die default
Wert für seinen Typ.
A feldgestützte Eigenschaft wird festgestellt als null-robust oder nicht, indem sie eine spezielle Analyse der nullbaren get
accessor.
- Für die Zwecke dieser Analyse,
field
wird vorübergehend angenommen, dass kommentiert Aufhebbarkeit, z.B.string?
. Dies verursachtfield
zu haben vielleicht-null oder maybe-default Ausgangszustand in derget
Accessor, abhängig von seinem Typ. - Wenn dann die Analyse des Getters „nullable“ keine Warnung „nullable“ ergibt, ist die Eigenschaft null-robust. Andernfalls ist es nicht null-robust.
- Wenn die Eigenschaft keinen Get-Accessor hat, ist sie (leer) null-resilient.
- Wenn der Get-Accessor automatisch implementiert wird, ist die Eigenschaft nicht null-resistent.
Die Nullbarkeit des Hintergrundfeldes wird wie folgt bestimmt:
- Wenn das Feld über Nullbarkeitsattribute wie
[field: MaybeNull]
,AllowNull
,NotNull
oderDisallowNull
verfügt, ist die Nullanmerkung des Felds identisch mit der nullablen Anmerkung der Eigenschaft.- Das liegt daran, dass wir, wenn der Benutzer anfängt, dem Feld Attribute für die Nullbarkeit zuzuweisen, nicht mehr auf irgendetwas schließen wollen, sondern nur noch wollen, dass die Nullbarkeit was der Benutzer sagte.
- Wenn die enthaltende Eigenschaft selbstvergessen oder kommentiert nullability, dann hat das Trägerfeld die gleiche nullability wie die Eigenschaft.
- Wenn die enthaltende Eigenschaft unkommentiert Nichtigkeit (z.B.
string
oderT
) oder hat die[NotNull]
Attribut, und die Eigenschaft ist null-robust, dann hat das hintere Feld kommentiert Nichtigkeit. - Wenn die enthaltende Eigenschaft unkommentiert Nichtigkeit (z.B.
string
oderT
) oder hat die[NotNull]
Attribut, und die Eigenschaft ist nicht nullspannungssicher, dann hat das hintere Feld unkommentiert Nichtigkeit.
Analyse der Konstrukteure
Gegenwärtig wird eine Auto-Eigenschaft ganz ähnlich wie ein gewöhnliches Feld in nullbare Konstruktoranalyse. Wir erweitern diese Behandlung auf feldgestützte Eigenschaften, durch die Behandlung jedes feldgestützte Eigenschaft als Stellvertreter für sein Hintergrundfeld.
Wir aktualisieren die folgende Spezifikation aus der vorherigen vorgeschlagener Ansatz um dies zu erreichen:
Bei jeder expliziten oder impliziten „Rückgabe“ in einem Konstruktor geben wir eine Warnung für jedes Mitglied aus, dessen Flusszustand mit seinen Anmerkungen und Nullbarkeitsattributen unvereinbar ist. Handelt es sich bei dem Element um eine feldgestützte Eigenschaft, wird die nullable-Anmerkung des zugrundeliegenden Feldes für diese Prüfung verwendet. Andernfalls wird die „nullable“-Anmerkung des Mitglieds selbst verwendet. Ein vernünftiger Ersatz dafür ist: Wenn die Zuweisung des Elements an sich selbst am Rückgabepunkt eine Nullbarkeitswarnung erzeugen würde, dann wird am Rückgabepunkt eine Nullbarkeitswarnung erzeugt.
Beachten Sie, dass dies im Wesentlichen eine eingeschränkte interprocedurale Analyse ist. Wir gehen davon aus, dass zur Analyse eines Konstruktors eine Bindungs- und „Null-Resilienz“-Analyse für alle anwendbaren Get-Accessoren desselben Typs erforderlich ist, die die field
kontextbezogenes Schlüsselwort und haben unkommentiert Nichtigkeit. Wir vermuten, dass dies nicht übermäßig kostspielig ist, da Getter-Bodies in der Regel nicht sehr komplex sind und dass die „Null-Resilience“-Analyse nur einmal durchgeführt werden muss, unabhängig davon, wie viele Konstruktoren der Typ enthält.
Analyse der Einrichter
Der Einfachheit halber verwenden wir die Begriffe „Setter“ und „Set-Accessor“, um entweder einen set
oder init
accessor.
Es ist zu prüfen, ob die Setzer von feldgestützte Eigenschaften das Hintergrundfeld tatsächlich initialisieren.
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
}
}
Der anfängliche Flusszustand des Unterstützungsfeld im Setter einer field-backed property wird wie folgt bestimmt:
- Wenn die Eigenschaft einen Initialisierer hat, dann ist der anfängliche Flusszustand derselbe wie der Flusszustand der Eigenschaft nach dem Besuch des Initialisierers.
- Andernfalls ist der anfängliche Flusszustand derselbe wie der von
field = default;
angegebene Flusszustand.
Bei jedem expliziten oder impliziten „Return“ im Setter wird eine Warnung ausgegeben, wenn der Flussstatus des Unterstützungsfeld ist mit seinen Anmerkungen und Nullbarkeitsattributen unvereinbar.
Hinweise
Diese Formulierung ist absichtlich sehr ähnlich zu gewöhnlichen Feldern in Konstruktoren. Da sich im Wesentlichen nur die Eigenschaftszugriffsfunktionen auf das Hintergrundfeld beziehen können, wird der Setter als „Mini-Konstruktor“ für das Hintergrundfeld behandelt.
Ähnlich wie bei normalen Feldern wissen wir in der Regel, dass die Eigenschaft im Konstruktor initialisiert wurde, weil sie festgelegt wurde, aber nicht unbedingt. Einfach innerhalb einer Verzweigung zurückkehren, in der Prop != null
wahr war, ist auch gut genug für unsere Konstruktoranalyse, da wir verstehen, dass nicht verfolgte Mechanismen verwendet worden sein können, um die Eigenschaft zu setzen.
Es wurden Alternativen in Betracht gezogen; siehe die Alternativen zur Aufhebbarkeit Abschnitt.
nameof
An Orten, an denen field
ist ein Schlüsselwort, nameof(field)
wird nicht kompiliert (LDM-Entscheidung), wie nameof(nint)
. Es ist nicht wie nameof(value)
, die zu verwenden ist, wenn Eigenschaftssetzer ArgumentException auslösen, wie es einige in den .NET Core-Bibliotheken tun. Im Gegensatz dazu weist nameof(field)
keine erwarteten Anwendungsfälle auf.
Überschreibungen
Überschreibende Eigenschaften können verwenden field
. Solche Verwendungen von field
beziehen sich auf das Hintergrundfeld für die überschreibende Eigenschaft, getrennt vom Hintergrundfeld der Basiseigenschaft, falls diese eines hat. Es gibt keine ABI, um das Backing-Feld einer Basiseigenschaft für überschreibende Klassen freizugeben, da dies die Kapselung brechen würde.
Wie bei den Auto-Eigenschaften können Eigenschaften, die die field
Schlüsselwort und eine Basiseigenschaft überschreiben, müssen alle Accessors überschreiben (LDM-Entscheidung).
Erfassung
field
sollten in lokalen Funktionen und Lambdas erfasst werden können, und Verweise auf field
innerhalb von lokalen Funktionen und Lambdas sind erlaubt, auch wenn es keine anderen Verweise gib (LDM-Entscheidung 1, LDM-Entscheidung 2):
public class C
{
public static int P
{
get
{
Func<int> f = static () => field;
return f();
}
}
}
Feldverwendungswarnungen
Wenn das Schlüsselwort field
in einem Accessor verwendet wird, wird die vorhandene Analyse des Compilers von nicht zugewiesenen oder ungelesenen Feldern dieses Feld einbeziehen.
- CS0414: Das Hintergrundfeld für die Eigenschaft 'Xyz' ist zugewiesen, aber sein Wert wird nie verwendet
- CS0649: Das Hintergrundfeld für die Eigenschaft „Xyz“ wird nie zugewiesen und hat immer seinen Standardwert
Spezifikationsänderungen
Syntax
Beim Kompilieren mit Sprachversion 14 oder höher, field
wird als Schlüsselwort betrachtet, wenn es als primäre Expression (LDM-Entscheidung) an den folgenden Standorten (LDM-Entscheidung):
- In Methodenkörpern von
get
,set
, andinit
Accessoren in Eigenschaften aber nicht Indexer - In Attributen, die auf diese Accessoren angewendet werden
- In verschachtelten Lambda-Ausdrücken und lokalen Funktionen sowie in LINQ-Ausdrücken in diesen Accessors
In allen anderen Fällen, einschließlich beim Kompilieren mit Sprachversion 12 oder niedriger, wird field
als Bezeichner betrachtet.
primary_no_array_creation_expression
: literal
+ | 'field'
| interpolated_string_expression
| ...
;
Eigenschaften
§15.7.1Eigenschaften - Allgemein
A eigenschafts_initialisator darf nur gegeben werden für
eine automatisch implementierte Eigenschaft, undeine Eigenschaft, die ein Hintergrundfeld hat, das ausgegeben werden soll. Die eigenschafts_initialisator bewirkt die Initialisierung des zugrunde liegenden Feldes solcher Eigenschaften mit dem Wert, der durch die Ausdruck.
§15.7.4Automatisch implementierte Eigenschaften
Eine automatisch implementierte Eigenschaft (oder kurz Auto-Eigenschaft) ist eine nicht-abstrakte, nicht-externe, nicht-ref-bewertete Eigenschaft mit
nur Semikolon-Accessor-Körper. Auto-Properties müssen einen get-Accessor haben und können optional einen set-Accessor haben.eines oder beide:
- ein Accessor mit einem nur aus Semikolon bestehenden Textkörper
- Verwendung des
field
kontextbezogenes Schlüsselwort innerhalb der Accessoren oderAusdruckskörper der EigenschaftWenn eine Eigenschaft als automatisch implementierte Eigenschaft angegeben wird, wird eine versteckte unbenannt das Feld für die Unterstützung ist automatisch für die Eigenscbarhaft verfügbar
, und die Zugriffsfunktionen sind so implementiert, dass sie von diesem Sicherungsfeld lesen und in dieses schreiben können. Bei Auto-Eigenschaften kann jedes Semikolon - nurget
implementiert ist, um von dort zu lesen, und jeder Semikolon-onlyset
Accessor, um in das zugehörige Hintergrundfeld zu schreiben.
Das ausgeblendete Backing-Feld ist unzugänglich, es kann nur über die automatisch implementierten Eigenschafts-Accessoren gelesen und geschrieben werden, auch innerhalb des enthaltenen Typs.Auf das Backing-Feld kann direkt mit der Funktionfield
Stichwortin allen Accessors und im Körper des Eigenschaftsausdrucks. Da das Feld unbenannt ist, kann es nicht in einernameof
Ausdruck.Wenn die Auto-Eigenschaft
kein gesetzter Zugriffspunktnur ein Semikolon-nur Get-Accessor, das hintere Feld wird alsreadonly
(§15.5.3). Genau wie einreadonly
Feld, eine schreibgeschützte Auto-Eigenschaft (ohne einen Set-Accessor oder einen Init-Accessor) kann auch im Körper eines Konstruktors der umschließenden Klasse zugewiesen werden. Eine solche Abtretung geht direkt an denread-onlyHintergrundfeld der Immobilie.Eine Auto-Eigenschaft darf nicht nur ein einziges Semikolon enthalten - nur
set
Accessor ohneget
accessor.Eine Auto-Eigenschaft kann optional mit einer eigenschafts_initialisator, direkt auf das Hintergrundfeld als Variablen_Initialisierer (§17.7).
Im Beispiel unten geschieht Folgendes:
// No 'field' symbol in scope.
public class Point
{
public int X { get; set; }
public int Y { get; set; }
}
ist gleichbedeutend mit der folgenden Erklärung:
// 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; } }
}
was gleichbedeutend ist mit:
// 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; } }
}
Im Beispiel unten geschieht Folgendes:
// No 'field' symbol in scope.
public class LazyInit
{
public string Value => field ??= ComputeValue();
private static string ComputeValue() { /*...*/ }
}
ist gleichbedeutend mit der folgenden Erklärung:
// No 'field' symbol in scope.
public class Point
{
private string __value;
public string Value { get { return __value ??= ComputeValue(); } }
private static string ComputeValue() { /*...*/ }
}
Alternativen
Alternativen zur Aufhebbarkeit
Zusätzlich zu den null-resilience Ansatz, der in der Aufhebbarkeit Die Arbeitsgruppe schlug der LDM die folgenden Alternativen zur Prüfung vor:
Keine Maßnahmen
Wir könnten hier überhaupt kein besonderes Verhalten einführen. In der Tat:
- Behandeln Sie eine feldgestützte Eigenschaft so, wie heute automatische Eigenschaften behandelt werden - sie muss im Konstruktor initialisiert werden, außer wenn sie als erforderlich gekennzeichnet ist, usw.
- Keine besondere Behandlung der Feldvariablen bei der Analyse von Eigenschaftsakzessoren. Es handelt sich einfach um eine Variable mit demselben Typ und derselben Nullierbarkeit wie die Eigenschaft.
Beachten Sie, dass dies zu lästigen Warnungen für „Lazy Property“-Szenarien führen würde; in diesem Fall müssten die Benutzer wahrscheinlich die null!
oder ähnliches, um Konstruktorwarnungen zu vermeiden.
Eine „Unteralternative“, die wir in Betracht ziehen können, besteht darin, auch Eigenschaften vollständig zu ignorieren, indem wir field
Schlüsselwort für die Analyse von nullbaren Konstruktoren. In diesem Fall gibt es keine Warnungen, dass der Benutzer etwas initialisieren muss, und es entstehen auch keine Unannehmlichkeiten für den Benutzer, unabhängig davon, welches Initialisierungsmuster er/sie verwendet.
Da wir nur den Versand der field
Schlüsselwort-Feature unter der Preview LangVersion in .NET 9, erwarten wir eine Möglichkeit, das Nullable-Verhalten für das Feature in .NET 10 zu ändern. Daher könnten wir in Betracht ziehen, eine "niedrigere Kosten"-Lösung wie diese kurzfristig zu übernehmen und langfristig bis zu einer der komplexeren Lösungen zu wachsen.
field
-gezielte Nullifizierbarkeitsattribute
Wir könnten die folgenden Standardeinstellungen einführen, um ein angemessenes Maß an Nullsicherheit zu erreichen, ohne dass dabei eine interprozedurale Analyse erforderlich ist.
- Die
field
Variable hat immer die gleiche Annotation „nullable“ wie die Eigenschaft. - Attribute der Nullbarkeit
[field: MaybeNull, AllowNull]
usw. können verwendet werden, um die Nullbarkeit des Hintergrundfelds anzupassen. - feldgestützte Eigenschaften werden in Konstruktoren auf der Grundlage der nullable-Annotation und der Attribute des Feldes auf Initialisierung geprüft.
- Setter in feldgestützten Eigenschaften prüfen auf Initialisierung von
field
ähnlich wie bei Konstrukteuren.
Das würde bedeuten, dass das „kleine faule Szenario“ stattdessen wie folgt aussehen würde:
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();
}
Ein Grund dafür, dass wir uns hier davor scheuen, Nullbarkeitsattribute zu verwenden, ist, dass die vorhandenen Attribute vor allem darauf ausgerichtet sind, Eingaben und Ausgaben von Signaturen zu beschreiben. Sie sind umständlich, um die Nullierbarkeit von langlebigen Variablen zu beschreiben.
- In der Praxis,
[field: MaybeNull, AllowNull]
ist erforderlich, damit sich das Feld „vernünftig“ wie eine löschbare Variable verhält, die den anfänglichen Flusszustand „vielleicht-null“ ergibt und in die mögliche Nullwerte geschrieben werden können. Dies erscheint den Nutzern für relativ häufige „wenig faule“ Szenarien zu mühsam. - Wenn wir diesen Ansatz verfolgen, würden wir eine Warnung hinzufügen, wenn
[field: AllowNull]
verwendet wird, wobei vorgeschlagen wird, auchMaybeNull
. Dies liegt daran, dass AllowNull allein nicht das leistet, was Benutzer von einer nullbaren Variablen erwarten: Es wird vorausgesetzt, dass das Feld anfangs nicht null ist, wenn noch nie etwas hineingeschrieben wurde. - Wir könnten auch das Verhalten von
[field: MaybeNull]
für das Schlüsselwortfield
oder sogar Felder im Allgemeinen anpassen, damit auch Null-Werte in die Variable geschrieben werden können, als wäreAllowNull
implizit ebenfalls vorhanden.
Beantwortete LDM-Fragen
Syntaxpositionen für Schlüsselwörter
Bei Accessoren, bei denen field
und value
an ein synthetisiertes Backing-Feld oder einen impliziten Setter-Parameter binden könnte, an welchen Syntaxstellen sollten die Bezeichner als Schlüsselwörter betrachtet werden?
- immer
- wichtigste Begriffe nur
- nie
In den ersten beiden Fällen handelt es sich um grundlegende Änderungen.
Wenn die Identifikatoren immer als Schlüsselwörter betrachtet werden, ist das zum Beispiel eine einschneidende Änderung für das Folgende:
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
}
}
Wenn die Bezeichner Schlüsselwörter sind, wenn sie als wichtigste Begriffe nur ist die Veränderung beim Brechen geringer. Die häufigste Unterbrechung ist die unqualifizierte Verwendung eines bestehenden Mitglieds namens field
.
class MyClass
{
private int field;
public int P => field; // binds to synthesized backing field rather than 'this.field'
}
Es gibt auch eine Pause, wenn field
oder value
in einer verschachtelten Funktion neu deklariert wird. Dies könnte die einzige Chance sein für value
für wichtigste Begriffe.
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
}
}
Wenn die Identifikatoren niemals als Schlüsselwörter betrachtet werden, binden die Bezeichner nur dann an ein synthetisiertes Hintergrundfeld oder den impliziten Parameter, wenn die Bezeichner nicht an andere Mitglieder gebunden sind. Für diesen Fall gibt es keine grundlegende Änderung.
Antwort
field
ist ein Schlüsselwort in entsprechenden Accessors, wenn es als primäre Expression nur; value
wird nie als Schlüsselwort betrachtet.
Szenarien ähnlich wie { set; }
{ set; }
ist derzeit nicht erlaubt und das macht Sinn: das dadurch erstellte Feld kann nie gelesen werden. Es gibt nun neue Möglichkeiten, in eine Situation zu kommen, in der der Setter ein Backing-Feld einführt, das nie gelesen wird, wie die Erweiterung von { set; }
in { set => field = value; }
.
Welche dieser Szenarien sollten kompiliert werden dürfen? Gehen Sie davon aus, dass die Warnung "Feld wird nie gelesen" genauso angewendet wird wie bei einem manuell definierten Feld.
-
{ set; }
- Heute verweigert, weiterhin verweigert { set => field = value; }
{ get => unrelated; set => field = value; }
{ get => unrelated; set; }
-
{ set { if (field == value) return; field = value; SendEvent(nameof(Prop), value); } }
-
{ get => unrelated; set { if (field == value) return; field = value; SendEvent(nameof(Prop), value); } }
Antwort
Nur das verbieten, was heute schon bei den Autoeigenschaften verboten ist, die körperlose set;
.
field
im Ereignis-Accessor
Sollte field
ein Schlüsselwort in einem Ereignis-Accessor sein, und sollte der Compiler ein Backing-Feld erzeugen?
class MyClass
{
public event EventHandler E
{
add { field += value; }
remove { field -= value; }
}
}
Empfehlung: field
ist nicht ein Schlüsselwort innerhalb eines Ereignis-Accessors, und es wird kein Hintergrundfeld erzeugt.
Antwort
Empfehlung angenommen. field
ist nicht ein Schlüsselwort innerhalb eines Ereignis-Accessors, und es wird kein Hintergrundfeld erzeugt.
Aufhebbarkeit von field
Sollte die vorgeschlagene Nichtigkeit von field
akzeptiert werden? Siehe die Aufhebbarkeit und die darin enthaltene offene Frage.
Antwort
Der allgemeine Vorschlag wird angenommen. Spezifisches Verhalten erfordert immer noch mehr Überprüfung.
field
im Eigenschaftsinitialisierer
Sollte field
ein Schlüsselwort in einem Eigenschaftsinitialisierer sein und an das Hintergrundfeld gebunden sein?
class A
{
const int field = -1;
object P1 { get; } = field; // bind to const (ok) or backing field (error)?
}
Gibt es sinnvolle Szenarien für den Verweis auf das Hintergrundfeld im Initialisierer?
class B
{
object P2 { get; } = (field = 2); // error: initializer cannot reference instance member
static object P3 { get; } = (field = 3); // ok, but useful?
}
Im obigen Beispiel sollte die Bindung an das Backing-Feld zu einem Fehler führen: „Initialisierer kann nicht auf nicht-statisches Feld verweisen“.
Antwort
Wir werden den Initialisierer wie in früheren Versionen von C# binden. Wir werden weder das Hintergrundfeld in den Geltungsbereich einbeziehen, noch werden wir verhindern, dass auf andere Mitglieder mit dem Namen field
.
Interaktion mit partiellen Eigenschaften
Initialisierer
Wenn eine Teileigenschaft die field
, Welche Teile sollten einen Initialisierer haben dürfen?
partial class C
{
public partial int Prop { get; set; } = 1;
public partial int Prop { get => field; set => field = value; } = 2;
}
- Es scheint klar, dass ein Fehler auftreten sollte, wenn beide Teile einen Initialisierer haben.
- Wir können uns Anwendungsfälle vorstellen, in denen entweder der Definitions- oder der Implementierungsteil den Anfangswert de
field
. - Es scheint, dass die Zulassung des Initialisierers im Definitionsteil den Entwickler effektiv zwingt,
field
zu verwenden, damit das Programm gültig ist. Ist das in Ordnung? - Wir glauben, dass es für Generatoren üblich ist,
field
zu verwenden, wenn in der Implementierung ein Hintergrundfeld desselben Typs erforderlich ist. Dies liegt daran, dass Generatoren ihren Benutzern häufig die Verwendung von[field: ...]
gezielten Attributen im Eigenschaftendefinitionsteil ermöglichen möchten. Die Verwendung desfield
Schlüsselwort erspart dem Generator-Implementierer die Mühe, solche Attribute an ein generiertes Feld „weiterzuleiten“ und die Warnungen über die Eigenschaft zu unterdrücken. Diese Generatoren möchten dem Benutzer wahrscheinlich auch erlauben, einen Anfangswert für das Feld anzugeben.
Empfehlung: Erlauben Sie einen Initialisierer für beide Teile einer partiellen Eigenschaft, wenn der Implementierungsteil die field
. Melden Sie einen Fehler, wenn beide Teile einen Initialisierer haben.
Antwort
Empfehlung angenommen. Entweder kann beim Deklarieren oder beim Implementieren von Eigenschaften ein Initialisierer verwendet werden, aber nicht beides gleichzeitig.
Auto-Accessoren
Wie ursprünglich entworfen, muss die partielle Implementierung von Eigenschaften Körper für alle Zugriffsmethoden haben. Allerdings haben die jüngsten Iterationen des field
Das Schlüsselwort-Feature hat das Konzept der "Auto-Accessoren" einbezogen. Sollten partielle Eigenschaftenimplementierungen solche Accessoren verwenden können? Wenn sie ausschließlich verwendet werden, ist sie von einer definierenden Deklaration nicht zu unterscheiden.
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.
Empfehlung: Verhindern Sie Auto-Accessoren in partiellen Implementierungen von Eigenschaften, da die Einschränkungen, wann sie verwendbar wären, verwirrender sind als der Nutzen, sie zuzulassen.
Antwort
Mindestens ein implementierender Accessor muss manuell implementiert werden, während der andere Accessor automatisch implementiert werden kann.
Schreibgeschütztes Feld
Wann sollte das synthetisierte Backing-Feld berücksichtigt werden? Nur-Lesezugriff?
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
}
Wenn das zugrunde liegende Feld betrachtet wird Nur-Lese-Modus, Das an die Metadaten ausgegebene Feld ist markiert. initonly
, und ein Fehler wird gemeldet, wenn field
wird anders als in einem Initialisierer oder Konstruktor geändert.
Empfehlung: Das synthetisierte Rückfeld ist Nur-Lesezugriff wenn der enthaltene Typ ein struct
wenn der enthaltene Typ ein readonly
.
Antwort
Empfehlung wird angenommen.
Schreibgeschützter Kontext und set
Sollte ein set
Zugriffsberechtigungen sollten in einem erlaubt sein. readonly
Kontext für eine Immobilie, die verwendet wird 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?
}
Antwort
Es könnte Szenarien geben, in denen Sie ein set
Zugriffsmodifikator auf a readonly
struktur und entweder durchreichen oder werfen. Wir werden dies zulassen.
[Conditional]
Code
Sollte das synthetisierte Feld generiert werden, wenn field
wird nur in ausgelassenen Aufrufen verwendet, um Bedingte Methoden?
Sollte in einem Nicht-DEBUG-Build ein Backing-Feld für das Folgende generiert werden?
class C
{
object P
{
get
{
Debug.Assert(field is null);
return null;
}
}
}
Zur Referenz, Felder für Primäre Konstruktorparameter werden in ähnlichen Fällen generiert -sehen sharplab.io.
Empfehlung: Das Hintergrundfeld wird erzeugt, wenn field
nur bei ausgelassenen Aufrufen von Bedingte Methoden.
Antwort
Conditional
Code kann Auswirkungen auf unbedingten Code haben, wie zum Beispiel Debug.Assert
Ändern der Nullbarkeit. Es wäre seltsam, wenn field
keine ähnlichen Auswirkungen hätte. Es ist auch unwahrscheinlich, dass es im meisten Code vorkommt, deshalb werden wir den einfachen Weg gehen und die Empfehlung annehmen.
Schnittstelleneigenschaften und Auto-Accessoren
Ist eine Kombination aus manuell und automatisch implementierten Accessoren für eine interface
Eigenschaft, bei der der automatisch implementierte Accessor auf ein synthetisiertes Backing-Feld verweist?
Für eine Instanzeigenschaft wird ein Fehler gemeldet, dass Instanzfelder nicht unterstützt werden.
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 { } }
}
Empfehlung: Auto-Accessoren werden erkannt in interface
Eigenschaften und die Auto-Accessoren verweisen auf ein synthetisiertes Backing-Feld. Für eine Instanzeigenschaft wird ein Fehler gemeldet, dass Instanzfelder nicht unterstützt werden.
Antwort
Das Standardisieren um das Instanzfeld selbst als Ursache des Fehlers ist mit partiellen Eigenschaften in Klassen konsistent, und wir sind mit diesem Ergebnis zufrieden. Die Empfehlung wird angenommen.
C# feature specifications