Dela via


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; och init; ä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;eller init;).

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:

  1. 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).

  2. 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 den field 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 att field har eventuellt null eller möjligen standard som inledande tillstånd inom accessorn get, 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, NotNulleller DisallowNullä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 eller T) eller har attributet [NotNull] och egenskapen är nulltolerant, har bakgrundsfältet kommenterad nullabilitet.
  • Om den innehållande egenskapen har okommerterad nullabilitet (t.ex. string eller T) 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, setoch init 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:

  1. en accessor med en kropp som endast består av ett semikolon
  2. användning av det field kontextuella nyckelordet inom egenskapensuttrycksblock eller

Nä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 semikolonbaserade get-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 nyckelordet fieldi 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ältet readonly (§15.5.3). Precis som i ett readonly-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 till skrivskyddade underliggande fältet för egenskapen.

En automatisk egenskap får inte ha endast en set-accessor med semikolon utan en get-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:

  1. Variabeln field har alltid samma nullbara annotering som egenskapen.
  2. Nullatribut [field: MaybeNull, AllowNull] osv. kan användas för att anpassa nullbarheten för bakgrundsfältet.
  3. fältbaserade egenskaper granskas för initialisering i konstruktorer baserat på fältets nullbara annoteringar och attribut.
  4. 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 till MaybeNull. 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å nyckelordet field, eller till och med fält i allmänhet, för att tillåta att null-värden också skrivs till variabeln, som om AllowNull 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?

  1. alltid
  2. endast primära uttryck
  3. 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.

  1. { set; } - Tillåts inte idag, fortsätt att inte tillåta
  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);
        }
    }
    

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: fieldinte 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 fieldrefereras 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 nyckelordet field 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.