field
nyckelord i egenskaper
Champion-utgåva: https://github.com/dotnet/csharplang/issues/8635
Sammanfattning
Utöka alla egenskaper så att de kan referera till ett automatiskt genererat bakgrundsfält med det nya kontextuella nyckelordet field
. Egenskaper kan nu också innehålla en åtkomstfunktion utan kropp tillsammans med en åtkomstfunktion med kropp.
Motivation
Automatiska egenskaper tillåter endast direkt inställning eller hämtar bakgrundsfältet, vilket endast ger viss kontroll genom att placera åtkomstmodifierare på åtkomstgivarna. Ibland finns det ett behov av att ha ytterligare kontroll över vad som händer i en eller båda accessorerna, men detta konfronterar användare med kostnaden för att deklarera ett bakgrundsfält. Namnet på bakgrundsfältet måste sedan hållas synkroniserat med egenskapen och bakgrundsfältet är begränsat till hela klassen, vilket kan leda till att åtkomsterna kringgås oavsiktligt inifrån klassen.
Det finns flera vanliga scenarier. I gettern finns det lat initiering eller standardvärden när egenskapen aldrig har angetts. I settern tillämpas en begränsning för att säkerställa giltigheten för ett värde, eller identifiera och sprida uppdateringar, till exempel genom att höja INotifyPropertyChanged.PropertyChanged
händelsen.
I dessa fall måste du alltid skapa ett instansfält och skriva hela egenskapen själv. Detta lägger inte bara till en hel del kod, utan läcker också bakgrundsfältet i resten av typens omfång, när det ofta är önskvärt att bara ha det tillgängligt för accessorernas organ.
Ordlista
Autoproperty: Kort för "automatiskt implementerad egenskap" (§15.7.4). Åtkomstmetoder på en automatisk egenskap saknar metodkropp. Både implementeringen och säkerhetskopieringslagringen tillhandahålls av kompilatorn. Automatiska egenskaper har
{ get; }
,{ get; set; }
eller{ get; init; }
.Autoaccessor: Kort för "automatiskt implementerad accessor". Det här är en accessor som inte har någon kropp. Både implementeringen och säkerhetskopieringslagringen tillhandahålls av kompilatorn.
get;
,set;
ochinit;
är automatiska accessorer.Fullständig: Det här är en accessor som har en kropp. Implementeringen tillhandahålls inte av kompilatorn, men lagringen kan fortfarande vara (som i exemplet
set => field = value;
).fältunderstödd egenskap: Det här är antingen en egenskap som använder nyckelordet
field
i kroppen av en accessor eller en automatisk egenskap.backningsfält: Det här är variabeln som anges av nyckelordet
field
i en egenskapens accessorer, som också implicit läses eller skrivs i automatiskt implementerade accessorer (get;
,set;
ellerinit;
).
Detaljerad design
För egenskaper med en init
-accessor gäller allt som gäller nedan för set
i stället för init
-accessorn.
Det finns två syntaxändringar:
Det finns ett nytt kontextuellt nyckelord,
field
, som kan användas i egenskapsaccessorer för att komma åt ett stödjande fält för egenskapsdeklarationen (LDM-beslut).Egenskaper kan nu kombinera automatiska accessorer med fullständiga accessorer (LDM-beslut). "Automatisk egenskap" kommer att fortsätta att betyda en egenskap vars accessorer inte har några kroppar. Inget av exemplen nedan betraktas som automatiska egenskaper.
Exempel:
{ get; set => Set(ref field, value); }
{ get => field ?? parent.AmbientValue; set; }
Båda accessorerna kan vara fullständiga accessorer med antingen en eller båda som använder 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();
}
}
Uttrycksegenskaper och egenskaper med endast en get
accessor kan också använda field
:
public string LazilyComputed => field ??= Compute();
public string LazilyComputed { get => field ??= Compute(); }
Egenskaper som endast kan sättas kan också använda field
:
{
set
{
if (field == value) return;
field = value;
OnXyzChanged(new XyzEventArgs(value));
}
}
Icke-bakåtkompatibla ändringar
Förekomsten av det kontextuella nyckelordet field
i egenskapsaccessorers kroppar är en potentiellt brytningsbar ändring.
Eftersom field
är ett nyckelord och inte en identifierare kan det bara "skuggas" av en identifierare genom den vanliga metoden för att undvika nyckelord: @field
. Alla identifierare med namnet field
som deklarerats i egenskapsåtkomstmetoder kan skydda mot avbrott vid uppgradering från C#-kodversioner före 14 genom att lägga till det första @
.
Om en variabel med namnet field
deklareras i en egenskapsåtkomst, rapporteras ett fel.
I språkversion 14 eller senare så rapporteras en varning om ett primärt uttryckfield
refererar till det bakomliggande fältet, men skulle ha hänvisat till en annan symbol i en tidigare språkversion.
Målgruppsanpassade attribut
Precis som med automatiska egenskaper kan alla egenskaper som använder ett bakgrundsfält i någon av dess accessorer använda fältriktade attribut:
[field: Xyz]
public string Name => field ??= Compute();
[field: Xyz]
public string Name { get => field; set => field = value; }
Ett fältriktattribut förblir ogiltigt om inte en accessor använder ett bakgrundsfält:
// ❌ Error, will not compile
[field: Xyz]
public string Name => Compute();
Egenskapsinitierare
Egenskaper med initierare kan använda field
. Bakgrundsfältet initieras direkt snarare än att settern anropas (LDM-beslut).
Att anropa en setter för en initierare är inte ett alternativ. Initializers bearbetas innan baskonstruktorer anropas, och det är inte tillåtet att anropa någon instansmetod innan baskonstruktorn anropas. Detta är också viktigt för standardinitiering/bestämd tilldelning av structs.
Detta ger flexibel kontroll över initieringen. Om du vill initiera utan att anropa setter använder du en egenskapsinitierare. Om du vill initiera genom att anropa en setter, tilldelar du egenskapen ett initialt värde i konstruktorn.
Här är ett exempel på var detta är användbart. Vi tror att nyckelordet field
kommer att hitta stor användning med vymodeller på grund av den eleganta lösningen det ger för INotifyPropertyChanged
-mönstret. Visa modellegenskapsuppsättningar är troligen databundna till användargränssnittet och kan orsaka ändringsspårning eller utlösa andra beteenden. Följande kod måste initiera standardvärdet för IsActive
utan att ange HasPendingChanges
till 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;
}
}
Den här skillnaden i beteende mellan en egenskapsinitierare och tilldelning från konstruktorn kan också ses med virtuella automatiska egenskaper i tidigare versioner av språket:
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");
}
}
}
Konstruktortilldelning
Precis som med automatiska egenskaper anropar tilldelningen i konstruktorn den (eventuellt virtuella) settern om den finns, och om det inte finns någon setter faller den tillbaka på att direkt tilldela till bakgrundsfältet.
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; }
}
Bestämd tilldelning i structs
Även om de inte kan refereras till i konstruktorn, omfattas stödfält som anges av nyckelordet field
av standardinitialisering och inaktiveringsvarningar som standard under samma villkor som andra structfält (LDM-beslut 1, LDM-beslut 2).
Till exempel (den här diagnostiken är tyst som standard):
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; }
}
Egenskaper som returnerar referenser
Precis som med automatiska egenskaper är nyckelordet field
inte tillgängligt för användning i egenskaper som returneras av referens. Referensreturnerande egenskaper kan inte ha set-åtkomstmetoder, och utan en set-accessor är get-accessorn och egenskapsinitiatören de enda som kan komma åt bakgrundsfältet. Utan användningsfall för detta är det inte tiden för referensåtergivande egenskaper att skrivas som autoegenskaper.
Nullbarhet
En princip för funktionen Nullable Reference Types var att förstå befintliga idiomatiska kodningsmönster i C# och att kräva så lite ceremoni som möjligt kring dessa mönster. Med field
nyckelordsförslag kan du använda enkla, idiomatiska mönster för att hantera scenarier som efterfrågas, till exempel lättinitierade egenskaper. Det är viktigt att de nullbara referenstyperna passar bra med de nya kodningsmönstren.
Mål:
En rimlig nivå av null-säkerhet bör säkerställas för olika användningsmönster för den
field
nyckelordsfunktionen.Mönster som använder nyckelordet
field
bör kännas som om de alltid har varit en del av språket. Undvik att göra så att användaren hoppar genom ringar för att aktivera nullbara referenstyper i kod som är helt idiomatisk för denfield
nyckelordsfunktionen.
Ett av de viktigaste scenarierna är lättinitierade egenskaper:
public class C
{
public C() { } // It would be undesirable to warn about 'Prop' being uninitialized here
string Prop => field ??= GetPropValue();
}
Följande nullabilitetsregler gäller inte bara för egenskaper som använder nyckelordet field
, utan även för befintliga automatiska egenskaper.
Det bakgrundsfältets nullbarhet
Se Ordlista för definitioner av nya termer.
Det bakgrundsfältet har samma typ som egenskapen. Den nullbara annoteringen kan dock skilja sig från egenskapen. För att fastställa den här nullbara annoteringen introducerar vi begreppet null-resilience.
Null-motståndskraft intuitivt innebär att egenskapens get
-accessor bevarar null-säkerhet även när fältet innehåller värdet default
för dess typ.
En fältbaserad egenskap bestäms vara null-elastisk eller inte genom att utföra en särskild nullbar analys av dess get
-accessor.
- I den här analysen antas
field
tillfälligt ha markerad nullbarhet, t.ex.string?
. Detta medför attfield
har eventuellt null eller möjligen standard som inledande tillstånd inom accessornget
, beroende på dess typ. - Om en nullbar analys av gettern inte ger några null-varningar är egenskapen null-elastisk. Annars är den inte nulltålig.
- Om egenskapen inte har någon get-accessor är den (per definition) null-säker.
- Om get-accessorn implementeras automatiskt är egenskapen inte null-elastisk.
Värdet för bakgrundsfältets nullbarhet bestäms på följande sätt:
- Om fältet har nullatribut som
[field: MaybeNull]
,AllowNull
,NotNull
ellerDisallowNull
är fältets nullbara kommentar samma som egenskapens nullbara anteckning.- Det beror på att när användaren börjar tillämpa nullatribut på fältet vill vi inte längre härleda något, vi vill bara att nullbarheten ska vara vad användaren sa.
- Om den innehållande egenskapen har omedveten eller kommenterad nullabilitet, har bakgrundsfältet samma nullabilitet som egenskapen.
- Om den innehållande egenskapen har inte kommenterad nullabilitet (t.ex.
string
ellerT
) eller har attributet[NotNull]
och egenskapen är nulltolerant, har bakgrundsfältet kommenterad nullabilitet. - Om den innehållande egenskapen har okommerterad nullabilitet (t.ex.
string
ellerT
) eller har[NotNull]
-attributet, och egenskapen inte är null-resilient, har bakgrundsfältet okommerterad nullabilitet.
Konstruktoranalys
För närvarande behandlas en automatisk egenskap på ungefär samma sätt som ett vanligt fält i nullbarhetsanalys av konstruktörer. Vi utökar den här behandlingen till fältbaserade egenskapergenom att behandla varje fältbaserad egenskap som en proxy för dess bakomliggande fält.
Vi uppdaterar följande specifikationsspråk från föregående föreslaget tillvägagångssätt för att uppnå detta:
Vid varje explicit eller implicit "retur" i en konstruktor ger vi en varning för varje medlem vars flödestillstånd är inkompatibelt med dess anteckningar och nullatribut. Om medlemmen är en fältbaserad egenskap används nullable-annoteringen för bakgrundsfältet för den här kontrollen. Annars används den nullbara anteckningen för själva medlemmen. En rimlig indikator för detta är: om det skulle ge en nullbarhetsvarning att tilldela medlemmen till sig själv vid returpunkten, så genereras en nullbarhetsvarning vid returpunkten.
Observera att detta i huvudsak är en begränsad interproceduranalys. Vi förväntar oss att det för att analysera en konstruktor är nödvändigt att göra en bindnings- och "null-motståndskraftsanalys" på alla tillämpliga get-användare av samma typ, som använder det field
kontextuella nyckelordet och har inte kommenterad nullability. Vi spekulerar i att detta inte är oöverkomligt dyrt eftersom getterkroppar vanligtvis inte är särskilt komplexa, och att analysen "null-resilience" bara behöver utföras en gång oavsett hur många konstruktorer som finns i typen.
Setter-analys
För enkelhetens skull använder vi termerna "setter" och "set accessor" för att referera till antingen en set
eller init
accessor.
Det finns ett behov av att kontrollera att fältbaserade egenskaper faktiskt initiera bakgrundsfältet.
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
}
}
Det inledande flödestillståndet för -bakgrundsfältet i en fältstödd egenskap bestäms på följande sätt:
- Om egenskapen har en initialiserare är det inledande flödestillståndet detsamma som flödestillståndet för egenskapen när du har besökt initieraren.
- Annars är det ursprungliga flödestillståndet detsamma som flödestillståndet som anges av
field = default;
.
Vid varje explicit eller implicit "retur" i settern rapporteras en varning om flödestillståndet för stödjefältet inte är kompatibelt med dess anvisningar och null-attribut.
Anmärkningar
Denna formulering är avsiktligt mycket lik vanliga fält i konstruktorer. Eftersom endast egenskapsåtkomsterna faktiskt kan referera till bakgrundsfältet behandlas setter som en "minikonstruktor" för bakgrundsfältet.
Precis som med vanliga fält vet vi vanligtvis att egenskapen initierades i konstruktorn eftersom den angavs, men inte nödvändigtvis. Att bara återvända inom en gren där Prop != null
var sant är också tillräckligt bra för vår konstruktoranalys, eftersom vi förstår att ospårade mekanismer kan ha använts för att ange egenskapen.
Alternativ har övervägts; se avsnittet Nullability-alternativ.
nameof
På platser där field
är ett nyckelord kan nameof(field)
inte kompilera (LDM-beslut), som nameof(nint)
. Det är inte som nameof(value)
, vilket är det som ska användas när egenskapssättare genererar ArgumentException som vissa gör i .NET Core-biblioteken. Däremot har nameof(field)
inga förväntade användningsfall.
Åsidosätter
Överordnade egenskaper kan använda field
. Sådana användningar av field
refererar till bakgrundsfältet för den åsidosatta egenskapen, separat från bakgrundsfältet för basegenskapen om det finns ett. Det finns ingen ABI för att exponera underliggande fältet hos en grundegenskap för klasser som överskriver, eftersom detta skulle bryta mot inkapslingen.
Precis som med automatiska egenskaper måste egenskaper som använder nyckelordet field
och åsidosätter en basegenskap åsidosätta alla åtkomstpunkter (LDM-beslut).
Skärmavbilder
field
bör kunna fångas in i lokala funktioner och lambdas, och referenser till field
inifrån lokala funktioner och lambdas tillåts även om det inte finns några andra referenser (LDM-beslut 1, LDM-beslut 2):
public class C
{
public static int P
{
get
{
Func<int> f = static () => field;
return f();
}
}
}
Fältanvändningsvarningar
När nyckelordet field
används i en accessor innehåller kompilatorns befintliga analys av otilldelade eller olästa fält det fältet.
- CS0414: Bakgrundsfältet för egenskapen "Xyz" tilldelas men dess värde används aldrig
- CS0649: Bakgrundsfältet för egenskapen "Xyz" tilldelas aldrig till och har alltid standardvärdet
Specifikationsändringar
Syntax
Vid kompilering med språkversion 14 eller senare betraktas field
som ett nyckelord när det används som ett primärt uttryck (LDM-beslut) på följande platser (LDM-beslut):
- I metodkroppar för
get
,set
ochinit
som accessorer i egenskaper men inte i indexerare - I attribut som tillämpas på dessa accessorer
- I kapslade lambda-uttryck och lokala funktioner och i LINQ-uttryck i dessa accessorer
I alla andra fall, inklusive vid kompilering med språkversion 12 eller lägre, betraktas field
som en identifierare.
primary_no_array_creation_expression
: literal
+ | 'field'
| interpolated_string_expression
| ...
;
Egenskaper
§15.7.1Fastigheter - Allmän
En property_initializer kan bara ges för
en automatiskt implementerad egenskap ochen egenskap som har ett bakgrundsfält som kommer att genereras. property_initializer orsakar initieringen av det underliggande fältet för sådana egenskaper med det värde som anges av -uttrycket.
§15.7.4Automatiskt implementerade egenskaper
En automatiskt implementerad egenskap (eller automatisk egenskap för kort) är en icke-abstrakt, icke-extern, icke-referensvärdesegenskap med
semikolonbaserade accessor-organ. Autoegenskaper ska ha en get-accessor och kan eventuellt ha en fast accessor.antingen eller båda av:
- en accessor med en kropp som endast består av ett semikolon
- användning av det
field
kontextuella nyckelordet inom egenskapensuttrycksblock ellerNär en egenskap anges som en automatiskt implementerad egenskap är ett dolt namnlöst säkerhetskopieringsfält automatiskt tillgängligt för egenskapen
och åtkomsterna implementeras för att läsa från och skriva till det bakgrundsfältet. För automatiska egenskaper implementeras alla semikolonbaseradeget
-accessorer för att läsa från och alla semikolonbaseradeset
-accessor för att skriva till dess bakgrundsfält.
Det dolda bakgrundsfältet är otillgängligt. Det kan endast läsas och skrivas via de automatiskt implementerade egenskapsåtkomsterna, även inom den innehållande typen.Det går att referera till bakgrundsfältet direkt med hjälp av nyckelordetfield
i alla åtkomster och i egenskapsuttryckets brödtext. Eftersom fältet är namnlöst kan det inte användas i ettnameof
uttryck.Om den automatiska egenskapen inte har
någon set-accessorendast en semikolonbaserad get-accessoranses bakgrundsfältetreadonly
(§15.5.3). Precis som i ettreadonly
-fält kan en skrivskyddad automatisk egenskap (utan en set-accessor eller en init-accessor) också tilldelas i kroppen av en konstruktor i den omslutande klassen. En sådan tilldelning tilldelar direkt tillskrivskyddadeunderliggande fältet för egenskapen.En automatisk egenskap får inte ha endast en
set
-accessor med semikolon utan enget
-accessor.En autoegenskap kan eventuellt ha en property_initializer, som tillämpas direkt på bakgrundsfältet som en variable_initializer (§17.7).
Följande exempel:
// No 'field' symbol in scope.
public class Point
{
public int X { get; set; }
public int Y { get; set; }
}
motsvarar följande deklaration:
// 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; } }
}
som motsvarar:
// 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; } }
}
Följande exempel:
// No 'field' symbol in scope.
public class LazyInit
{
public string Value => field ??= ComputeValue();
private static string ComputeValue() { /*...*/ }
}
motsvarar följande deklaration:
// No 'field' symbol in scope.
public class Point
{
private string __value;
public string Value { get { return __value ??= ComputeValue(); } }
private static string ComputeValue() { /*...*/ }
}
Alternativ
Alternativ för nullabilitet
Utöver den metod för null-motståndskraft som beskrivs i avsnittet Nullability föreslog arbetsgruppen följande alternativ för LDM:s övervägande:
Gör ingenting
Vi kan inte presentera något särskilt beteende alls här. I praktiken:
- Behandla en fältstödd egenskap på samma sätt som automatiska egenskaper behandlas idag – måste initieras i konstruktorn förutom när den markeras som obligatorisk osv.
- Ingen särskild behandling av fältvariabeln vid analys av egenskapsåtkomster. Det är helt enkelt en variabel med samma typ och nullabilitet som egenskapen.
Observera att detta skulle resultera i olägenhetsvarningar för scenarier med lata egenskaper, i vilket fall användarna sannolikt skulle behöva tilldela null!
eller liknande för att tysta konstruktorvarningar.
Ett "underalternativ" som vi kan överväga är att helt ignorera egenskaper med hjälp av field
nyckelord för nullbar konstruktoranalys. I så fall skulle det inte finnas några varningar någonstans om att användaren behöver initiera något, men inte heller någon olägenhet för användaren, oavsett vilket initieringsmönster de använder.
Eftersom vi bara planerar att införa field
-nyckelordsfunktionen i förhandsversion av LangVersion i .NET 9 förväntar vi oss att ha viss möjlighet att ändra det nullvärdesbeteendet för funktionen i .NET 10. Därför kan vi överväga att anta en "billigare" lösning som den här på kort sikt och växa upp till en av de mer komplexa lösningarna på lång sikt.
field
–målattribut för nullabilitet
Vi skulle kunna införa följande standardvärden för att uppnå en rimlig nivå av nollsäkerhet, utan att alls innefatta någon interproceduranalys:
- Variabeln
field
har alltid samma nullbara annotering som egenskapen. - Nullatribut
[field: MaybeNull, AllowNull]
osv. kan användas för att anpassa nullbarheten för bakgrundsfältet. - fältbaserade egenskaper granskas för initialisering i konstruktorer baserat på fältets nullbara annoteringar och attribut.
- Setters i fältbaserade egenskaper kontrollerar initiering av
field
på ett liknande sätt som konstruktorer.
Detta skulle innebära att "little-l lazy scenario" skulle se ut så här istället:
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();
}
En anledning till att vi drog oss undan från att använda nullatribut här är att de vi har verkligen är inriktade på att beskriva indata och utdata från signaturer. De är besvärliga att använda för att beskriva nullbarheten för långlivade variabler.
- I praktiken krävs
[field: MaybeNull, AllowNull]
för att fältet ska bete sig "rimligt" som en nullable variabel, vilket ger ett initialt flödestillstånd som kan vara null och tillåter att möjliga nullvärden skrivs till det. Detta känns besvärligt att be användare att göra för relativt vanliga små lata scenarier. - Om vi har följt den här metoden kan vi överväga att lägga till en varning när
[field: AllowNull]
används, vilket tyder på att även lägga tillMaybeNull
. Det beror på att AllowNull i sig inte gör vad användarna behöver av en nullbar variabel: det förutsätter att fältet ursprungligen inte är null när vi aldrig har sett något skriva till det ännu. - Vi kan också överväga att justera beteendet för
[field: MaybeNull]
på nyckelordetfield
, eller till och med fält i allmänhet, för att tillåta att null-värden också skrivs till variabeln, som omAllowNull
också var implicit närvarande.
Besvarade LDM-frågor
Syntaxplatser för nyckelord
I de accessorer där field
och value
kan binda till ett syntetiserat bakgrundsfält eller en implicit setterparameter, i vilka syntaxpositioner ska identifierarna betraktas som nyckelord?
- alltid
- endast primära uttryck
- aldrig
De två första fallen är brytande ändringar.
Om identifierarna alltid anses vara nyckelord, är det en icke-bakåtkompatibel ändring för följande till exempel:
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
}
}
Om identifierarna är nyckelord endast när de används som primära uttryck är förändringen mindre. Det vanligaste felet kan vara okvalificerad användning av en befintlig medlem med namnet field
.
class MyClass
{
private int field;
public int P => field; // binds to synthesized backing field rather than 'this.field'
}
Det finns också en paus när field
eller value
deklareras om i en kapslad funktion. Det här kan vara den enda pausen för value
för primära uttryck.
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
}
}
Om identifierarna och aldrig anses vara nyckelord, kommer de endast att binda till ett syntetiserat bakgrundsfält eller den implicita parametern när identifierarna inte binder till andra medlemmar. Det finns ingen större förändring för det här fallet.
Svar
field
är ett nyckelord i lämpliga accessorer när det endast används som ett primärt uttryck. value
betraktas aldrig som ett nyckelord.
Scenarier som liknar { set; }
{ set; }
tillåts för närvarande inte och det är vettigt: det fält som detta skapar kan aldrig läsas. Det finns nu nya sätt att hamna i en situation där en setter introducerar ett stödjefält som aldrig läses, till exempel utvidgningen av { set; }
till { set => field = value; }
.
Vilket av dessa scenarier bör tillåtas kompilera? Anta att varningen "fältet läses aldrig" skulle gälla precis som med ett manuellt deklarerat fält.
-
{ set; }
- Tillåts inte idag, fortsätt att inte tillåta { 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); } }
Svar
Endast förbjuda vad som redan är förbjudet i dag i de automatiska egenskaperna, den kroppslösa set;
.
field
i händelseåtkomst
Ska field
vara ett nyckelord i en händelseåtkomst och ska kompilatorn generera ett bakgrundsfält?
class MyClass
{
public event EventHandler E
{
add { field += value; }
remove { field -= value; }
}
}
Rekommendation: field
inte ett nyckelord i en händelseaccessor, och inget tillhörande fält genereras.
Svar
Rekommendationen är tagen.
field
är inte ett nyckelord i en händelseaccessor, och inget underliggande fält genereras.
Ogiltighet för field
Bör den föreslagna ogiltigheten av field
godkännas? Se avsnittet Nullability och den öppna frågan där i.
Svar
Ett allmänt förslag antas. Det specifika beteendet behöver fortfarande granskas mer.
field
i egenskapsinitieraren
Ska field
vara ett nyckelord i en egenskapsinitierare och binda till bakgrundsfältet?
class A
{
const int field = -1;
object P1 { get; } = field; // bind to const (ok) or backing field (error)?
}
Finns det användbara scenarier för att referera till bakgrundsfältet i initieraren?
class B
{
object P2 { get; } = (field = 2); // error: initializer cannot reference instance member
static object P3 { get; } = (field = 3); // ok, but useful?
}
I exemplet ovan bör bindning till bakgrundsfältet resultera i ett fel: "initieraren kan inte referera till icke-statiskt fält".
Svar
Vi binder initieraren som i tidigare versioner av C#. Vi kommer inte att inkludera bakgrundsfältet i omfånget och vi kommer inte heller att förhindra att andra medlemmar med namnet field
refereras till.
Interaktion med partiella egenskaper
Initialisatorer
Vilka delar ska tillåtas ha en initialiserare när en partiell egenskap använder field
?
partial class C
{
public partial int Prop { get; set; } = 1;
public partial int Prop { get => field; set => field = value; } = 2;
}
- Det är tydligt att ett fel bör inträffa när båda delarna har en initialisator.
- Vi kan tänka oss användningsfall där antingen definitionen eller implementeringsdelen kanske vill ange det ursprungliga värdet för
field
. - Det verkar som om vi tillåter initiatorn för definitionsdelen, det tvingar effektivt implementeraren att använda
field
för att programmet ska vara giltigt. Är det okej? - Vi tror att det är vanligt att generatorer använder
field
när ett stödfält av samma typ behövs i implementeringen. Detta beror delvis på att generatorer ofta vill göra det möjligt för användarna att använda[field: ...]
riktade attribut för egenskapsdefinitionsdelen. Genom att använda nyckelordetfield
slipper generatorimplementeraren besväret med att "vidarebefordra" sådana attribut till något genererat fält och behöver inte heller undertrycka varningarna på egenskapen. Det är troligt att samma generatorer också vill tillåta att användaren anger ett initialt värde för fältet.
Rekommendation: Tillåt en initialisering av vilken del som helst av en partiell egenskap när implementeringsdelen använder field
. Rapportera ett fel om båda delarna har en initiator.
Svar
Rekommendation godkänd. Antingen kan deklarering eller implementering av egenskapsplatser använda en initialiserare, men inte båda samtidigt.
Automatiska Accessorer
Som ursprungligen utformades måste partiell egenskapsimplementering ha organ för alla accessorer. De senaste iterationerna av field
nyckelordsfunktionen har dock inkluderat begreppet "autoaccessorer". Ska implementeringar av partiella egenskaper kunna använda sådana accessorer? Om de endast används kan det inte skiljas från en definierande deklaration.
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.
Rekommendation: Tillåt inte automatiska åtkomster i partiella egenskapsimplementeringar, eftersom begränsningarna kring när de skulle vara användbara är mer förvirrande att följa än fördelen med att tillåta dem.
Svar
Minst en implementerande accessor måste implementeras manuellt, men den andra accessorn kan implementeras automatiskt.
Skrivskyddat fält
När ska det genererade bakgrundsfältet betraktas som skrivskyddat?
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
}
När ett bakgrundsfält anses vara endast-läsbart, markeras fältet som skickas till metadata med initonly
, och ett fel rapporteras om field
ändras på annat sätt än i en initierare eller konstruktor.
Rekommendation: Det syntetiserade bakgrundsfältet är skrivskyddat när den innehållande typen är en struct
och egenskapen eller den innehållande typen deklareras readonly
.
Svar
Rekommendationen godkänns.
Skrivskyddad kontext och set
Ska en set
accessor tillåtas i en readonly
kontext för en egenskap som använder 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?
}
Svar
Det kan finnas scenarier för detta där du implementerar en set
-accessor på en readonly
struct och antingen skickar den genom eller genererar. Vi kommer att tillåta detta.
[Conditional]
kod
Ska det syntetiserade fältet genereras när field
endast används i utelämnade anrop till villkorsstyrda metoder?
Ska till exempel ett säkerhetskopieringsfält genereras för följande i en icke-DEBUG-version?
class C
{
object P
{
get
{
Debug.Assert(field is null);
return null;
}
}
}
Som referens genereras fält för primära konstruktorparametrar i liknande fall – se sharplab.io.
Rekommendation: Bakgrundsfältet genereras när field
endast används i utelämnade anrop till villkorsstyrda metoder.
Svar
Conditional
-kod kan ha effekter på icke-villkorsstyrd kod, till exempel kan Debug.Assert
ändra nullability. Det skulle vara konstigt om field
inte hade liknande effekter. Det är också osannolikt att det kommer upp i de flesta kod, så vi kommer att göra det enkla och acceptera rekommendationen.
Gränssnittsegenskaper och automatiska tillgångar
Identifieras en kombination av manuellt och automatiskt implementerade accessorer för en interface
-egenskap där den automatiskt implementerade accessorn refererar till ett syntetiserat bakgrundsfält?
För en instansegenskap rapporteras ett fel om att instansfält inte stöds.
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 { } }
}
Rekommendation: Automatiska åtkomster identifieras i interface
egenskaper och de automatiska åtkomsterna refererar till ett syntetiserat bakgrundsfält. För en instansegenskap rapporteras ett fel om att instansfält inte stöds.
Svar
Standardisering runt själva instansfältet som orsaken till felet är konsekvent med partiella egenskaper i klasser, och vi gillar det resultatet. Rekommendationen godkänns.
C# feature specifications