Sdílet prostřednictvím


klíčové slovo field ve vlastnostech

Problém šampiona: https://github.com/dotnet/csharplang/issues/8635

Shrnutí

Rozšiřte všechny vlastnosti, aby bylo možné odkazovat na automaticky vygenerované záložní pole pomocí nového kontextového klíčového slova field. Vlastnosti teď můžou obsahovat také příslušenství bez těla spolu s příslušenstvím s tělem.

Motivace

Automatické vlastnosti umožňují pouze přímé nastavení nebo získání podkladového pole, což umožňuje určitou kontrolu umístěním modifikátorů přístupu na přístupové metody. Někdy je potřeba mít další kontrolu nad tím, co se stane v jednom nebo obou přístupových objektech, ale to představuje pro uživatele režijní náklady na deklarování pomocného pole. Název záložního pole se pak musí udržovat synchronizovaný s vlastností a záložní pole je omezeno na celou třídu, což může vést k náhodnému obejití přístupových metod zevnitř třídy.

Existuje několik běžných scénářů. V rámci getteru dochází k opožděné inicializaci, nebo se používají výchozí hodnoty, když vlastnost nebyla nikdy zadána. V rámci setter platí omezení, které zajišťuje platnost hodnoty nebo zjišťování a šíření aktualizací, jako je vyvolání INotifyPropertyChanged.PropertyChanged události.

V těchto případech teď musíte vždy vytvořit pole instance a napsat celou vlastnost sami. To nejen přidává poměrně velké množství kódu, ale také umožňuje, že podpůrné pole uniká do zbytku rozsahu typu, když je často žádoucí, aby bylo k dispozici pouze pro těla přístupových metod.

Glosář

  • Automatické vlastnosti: Zkratka pro "automaticky implementovanou vlastnost" (§15.7.4). Přístupové objekty v automatické vlastnosti nemají žádné tělo. Implementace i záložní úložiště poskytuje kompilátor. Automatické vlastnosti mají { get; }, { get; set; }nebo { get; init; }.

  • automatického příslušenství: Zkratka pro "automaticky implementované příslušenství". Toto je příslušenství, které nemá žádné tělo. Implementace i záložní úložiště poskytuje kompilátor. get;, set; a init; jsou automatické přístupové objekty.

  • full accessor: Toto je příslušenství, které má tělo. Implementace není poskytována kompilátorem, i když podpůrná paměť může stále být (jako v příkladu set => field = value;).

  • vlastnost s podporou pole: Jedná se o vlastnost, která používá klíčové slovo field v těle akcesoru, nebo automatickou vlastnost.

  • pomocné pole: Toto je proměnná označená klíčovým slovem field v přístupech vlastnosti, která je také implicitně přečtena nebo zapsána v automaticky implementovaných přístupech (get;, set;nebo init;).

Podrobný návrh

U vlastností s přístupovým objektem init se místo toho, aby se vztahovalo níže uvedené na set, uplatňuje na přístupový objekt init.

Existují dvě změny syntaxe:

  1. Existuje nové kontextové klíčové slovo, field, které lze použít v těle přistupujících metod vlastností pro přístup k podkladovému poli pro deklaraci vlastnosti (rozhodnutí LDM).

  2. Vlastnosti nyní mohou kombinovat automatickými přístupovými metodami s úplnými přístupovými metodami (rozhodnutí LDM). "Automatická vlastnost" bude nadále znamenat vlastnost, jejíž přístupové objekty nemají žádné tělo. Žádný z níže uvedených příkladů nebude považován za automatické vlastnosti.

Příklady:

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

Oba přístupové prvky mohou být kompletními metodami, přičemž jeden nebo oba mohou využívat 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();
    }
}

Vlastnosti s výrazem na tělo a vlastnosti pouze s přístupovou metodou get mohou také používat field:

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

Vlastnosti pouze pro nastavení mohou také používat field:

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

Zásadní změny

Existence kontextového klíčového slova field v rámci objektů přistupování vlastností je potenciálně zásadní změna.

Vzhledem k tomu, že field je klíčové slovo, nikoli identifikátor, může být 'stínován' pouze identifikátorem pomocí běžné metody úniku klíčových slov: @field. Všechny identifikátory s názvem field, deklarované v rámci těl přístupových metod vlastností, mohou ochránit před přerušením při upgradu z verzí C# před verzí 14 začleněním úvodního @.

Pokud je proměnná s názvem field deklarována v přístupovém objektu vlastnosti, zobrazí se chyba.

V jazykové verzi 14 nebo novější se zobrazí upozornění, pokud primární výrazfield odkazuje na podkladové pole, ale v dřívější jazykové verzi by odkazoval na jiný symbol.

Atributy cílené na pole

Stejně jako u automatických vlastností budou moci všechny vlastnosti, které používají podkladové pole v některém ze svých přístupových metod, používat atributy cílené na pole.

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

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

Atribut cílený na pole zůstane neplatný, pokud příslušenství nepoužívá záložní pole:

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

Inicializátory vlastností

Vlastnosti s inicializátory mohou používat field. Pomocné pole je přímo inicializováno namísto volání setteru (rozhodnutí LDM).

Volání setteru pro inicializátor není volbou; inicializátory jsou zpracovány před voláním základních konstruktorů a je zakázáno volat jakoukoli instanční metodu před voláním základního konstruktoru. To je také důležité pro výchozí inicializaci nebo určité přiřazení struktur.

To přináší flexibilní kontrolu nad inicializací. Pokud chcete inicializovat bez volání na setter, použijte inicializátor vlastnosti. Pokud chcete inicializovat pomocí volání setteru, přiřaďte vlastnosti počáteční hodnotu v konstruktoru.

Tady je příklad toho, kde je to užitečné. Věříme, že klíčové slovo field najde široké využití v rámci modelů zobrazení díky elegantnímu řešení, které přináší pro šablonu INotifyPropertyChanged. Settery vlastností modelu zobrazení budou pravděpodobně napojené na uživatelské rozhraní a pravděpodobně způsobí sledování změn nebo aktivují jiná chování. Následující kód musí inicializovat výchozí hodnotu IsActive bez nastavení HasPendingChanges na 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 (RuntimeHelpers.Equals(location, value))
            return false;

        location = value;
        HasPendingChanges = true;
        return true;
    }
}

Tento rozdíl v chování mezi inicializátorem vlastností a přiřazením z konstruktoru lze také vidět s virtuálními automatickými vlastnostmi v předchozích verzích jazyka:

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");
        }
    }
}

Přiřazení konstruktoru

Podobně jako u automatických vlastností, přiřazení v konstruktoru volá setter (pokud existuje), a pokud žádný setter neexistuje, vrátí se k přímému přiřazení do záložního pole.

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

Určité přiřazení ve structech

I když na ně nelze v konstruktoru odkazovat, backing pole označená klíčovým slovem field podléhají výchozí inicializaci a varováním o zakázání ve výchozím nastavení za stejných podmínek, jako u všech ostatních polí struktury (rozhodnutí LDM 1, rozhodnutí LDM 2).

Například (tato diagnostika je ve výchozím nastavení bezobslužná):

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

Vlastnosti vracející odkaz

Stejně jako u automatických vlastností nebude klíčové slovo field k dispozici pro použití v návratových vlastnostech. Návratové vlastnosti nemohou mít nastavené přístupové objekty a bez přístupového objektu set, přístupové objekty get a inicializátor vlastností by byly jedinými věcmi, které mají přístup k záložnímu poli. Případy použití tohoto chybí; nyní není vhodná doba na to, aby vlastnosti vracející reference mohly být psány jako automatické vlastnosti.

Nullovatelnost

Principem funkce Nullable Reference Types bylo pochopení stávajících idiomatických vzorů kódování v jazyce C# a vyžadování co nejmenšího obřadu kolem těchto vzorů. Návrh klíčového slova field umožňuje jednoduché idiomatické vzory řešit široce požadované scénáře, jako jsou vlastnosti inicializované až na vyžádání. Je důležité, aby nulovatelné referenční typy dobře zapadaly do těchto nových vzorů kódování.

Cíle:

  • Pro různé vzory použití funkce klíčového slova field je třeba zajistit přiměřenou úroveň bezpečnosti s hodnotou null.

  • Vzory, které používají klíčové slovo field, by se měly cítit jako by vždy byly součástí jazyka. Vyhněte se tomu, aby uživatel musel překonávat překážky pro povolení typů odkazů s možnou hodnotou Null v kódu, který je pro funkci klíčového slova field zcela idiomatický.

Jedním z klíčových scénářů je líná inicializace vlastností.

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

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

Následující pravidla nullability se použijí nejen na vlastnosti, které používají klíčové slovo field, ale také pro existující automatické vlastnosti.

Nullovatelnost záložního pole

Definice nových termínů najdete v glosáře.

Pole backing má stejný typ jako vlastnost. Jeho poznámka s možnou hodnotou null se však může lišit od vlastnosti. Abychom určili tuto anotaci s možnou hodnotou null, uvádíme koncept odolnosti vůči hodnotám null. odolnost vůči hodnotám null intuitivně znamená, že přístupový prvek get vlastnosti zachovává bezpečnost při použití null i když pole obsahuje hodnotu default pro svůj typ.

Vlastnost založená na poli je určena, zda je odolná proti hodnotě null či nikoli, provedením speciální analýzy s možnou hodnotou null svého accesoru get.

  • Pro účely této analýzy se dočasně předpokládá, že fieldanotovanou nulovatelnost, např. string?. To způsobí, že field může mít možná-nulový nebo mít možná-výchozí počáteční stav v přistupovači get, v závislosti na jeho typu.
  • Pokud analýza getteru s možnou hodnotou null nevygeneruje žádná upozornění na hodnoty null, vlastnost je odolná proti hodnotě null. Jinak není odolné proti hodnotě null .
  • Pokud vlastnost nemá přístupové objekty get, je (vacuously) odolná proti hodnotě null.
  • Pokud je přístupový objekt get automaticky implementován, vlastnost není odolná vůči hodnotě null.

Hodnota nullability záložního pole je určena následujícím způsobem:

  • Pokud má pole atributy nullability, jako jsou [field: MaybeNull], AllowNull, NotNullnebo DisallowNull, pak je poznámka k poli s možnou hodnotou null stejná jako anotace s možnou hodnotou null vlastnosti.
    • Je to proto, že když uživatel začne na pole používat atributy nullability, už nechceme nic odvodit, chceme, aby byla nulová možnost to, co uživatel řekl.
  • Pokud vlastnost obsahující oblivious nebo anotovanou hodnotu nullability, pak má podkladové pole stejnou hodnotu nullability jako vlastnost.
  • Pokud obsahující vlastnost má neanotovanou nullability (např. string nebo T) nebo má atribut [NotNull] a vlastnost je odolná vůči nulovým hodnotám, pak má podkladové pole anotovanou nullability.
  • Pokud vlastnost obsahující obsahuje neonotované nullability (např. string nebo T) nebo má atribut [NotNull] a vlastnost je neníodolná proti hodnotě null , pak má backingové pole neonotovanou nullability.

Analýza konstruktoru

V současné době je automatická vlastnost zpracována velmi podobně jako běžné pole v analýzy konstruktoru s možnou hodnotou null. Tuto úpravu rozšiřujeme na polem podložené vlastnostitím, že s každou vlastností podloženou polem zacházíme jako s proxy k jejímu podkladovému poli.

Aktualizujeme následující specifikaci z předchozího přístupu na navrhovaný přístup, abychom toho dosáhli.

U každého explicitního nebo implicitního 'return' v konstruktoru dáváme varování pro každého člena, jehož stav toku není kompatibilní s jeho anotacemi a atributy nulovatelnosti. Pokud je členem vlastnost podporovaná polem, použije se pro tuto kontrolu nullable anotace záložního pole. V opačném případě se použije nullable anotace samotného prvku. Přiměřenou analogií pro toto je: pokud by přiřazení člena samo sobě v návratovém bodě vyvolalo upozornění na nulovost, pak se v návratovém bodě toto upozornění skutečně objeví.

Všimněte si, že se jedná v podstatě o omezenou interprocedrální analýzu. Předpokládáme, že aby bylo možné analyzovat konstruktor, bude nutné provést vázání a analýzu odolnosti vůči null u všech použitelných get přistupovačů ve stejném typu, které používají kontextové klíčové slovo field a mají nenotovanou nulovost. Spekulujeme, že to není příliš nákladné, protože těla getterů obvykle nejsou příliš složitá a že analýza „odolnosti proti chybám null“ musí být provedena pouze jednou, bez ohledu na to, kolik konstruktorů je v typu.

Analýza setteru

Pro zjednodušení používáme termíny "setter" a "set accessor" k odkazování na set nebo init příslušenství.

Je potřeba zkontrolovat, že settery vlastností , které se opírají o pole, skutečně inicializují vnitřní pole.

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

Počáteční stav toku backingového pole v setter vlastnosti založené na pole je určen následujícím způsobem:

  • Pokud vlastnost má inicializátor, počáteční stav toku je stejný jako stav toku vlastnosti po návštěvě inicializátoru.
  • V opačném případě je počáteční stav toku stejný jako stav toku zadaný field = default;.

Při každém explicitním nebo implicitním návratu v setteru se zobrazí upozornění, pokud stav toku pomocného pole není kompatibilní s jeho anotacemi a atributy nulovatelnosti.

Poznámky

Tato formulace je záměrně velmi podobná obvyklým polím u konstruktorů. V podstatě protože pouze přístupové metody vlastností mohou ve skutečnosti odkazovat na podkladové pole, je setter považován za "minikonstruktor" pro podkladové pole.

Podobně jako u běžných polí obvykle víme, že vlastnost byla inicializována v konstruktoru, protože byla nastavena, ale ne nutně. Prosté návraty do větve, kde Prop != null je pravdivá, jsou pro naši analýzu konstruktoru také dostačující, protože chápeme, že k nastavení vlastnosti mohly být použity nesledované mechanismy.

Byly zvažovány alternativy; viz části alternativy pro nullovatelnost.

nameof

Na místech, kde je field klíčovým slovem, se nameof(field) nepodaří přeložit (rozhodnutí LDM), například nameof(nint). Není to jako nameof(value), co použít, když nastavovače vyvolají ArgumentException jako některé to dělají v knihovnách .NET Core. Naproti tomu nameof(field) nemá žádné očekávané případy použití.

Přepíše

Přepsání vlastností může používat field. Taková použití field odkazují na záložní pole pro přepisující vlastnost, odděleně od záložního pole základní vlastnosti, pokud má jedno. Neexistuje žádné ABI pro zpřístupnění podkladového pole základní vlastnosti třídám, které ji přepisují, protože by to narušilo zapouzdření.

Stejně jako u automatických vlastností, vlastnosti, které používají klíčové slovo field a přepisují základní vlastnost, musí přepsat všechny akcesory (rozhodnutí LDM).

Uchvacuje

field mohou být zachyceny ve funkcích a lambdách a odkazy na field z funkcí a lambd jsou povoleny, i když nejsou žádné jiné odkazy (rozhodnutí LDM 1, rozhodnutí LDM 2):

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

Upozornění na použití polí

Při použití klíčového slova field v přístupovém objektu bude stávající analýza nepřiřazených nebo nepřečtených polí kompilátoru obsahovat toto pole.

  • CS0414: Záložní pole vlastnosti Xyz je přiřazeno, ale jeho hodnota se nikdy nepoužívá.
  • CS0649: Základní pole pro vlastnost 'Xyz' není nikdy přiřazeno a bude mít vždy svoji výchozí hodnotu.

Změny specifikace

Syntax

Při kompilaci s jazykovou verzí 14 nebo vyšší se field považuje za klíčové slovo, pokud se používá jako primární výraz (rozhodnutí LDM) na následujících místech (rozhodnutí LDM):

  • V tělech metod přistupujících k get, seta init ve vlastnostech , ale nikoli v indexerech
  • V atributech použitých u těchto přístupových objektů
  • Ve vnořených výrazech lambda a místních funkcích a ve výrazech LINQ v těchto přístupových objektech

Ve všech ostatních případech, včetně kompilace s jazykem verze 12 nebo nižší, se field považuje za identifikátor.

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

Vlastnosti

§15.7.1Vlastnosti - Obecné

property_initializer lze udělit pouze pro automaticky implementovanou vlastnost avlastnost, která má záložní pole, které se vygeneruje. property_initializer způsobí inicializaci podkladového pole těchto vlastností s hodnotou danou výrazem .

§15.7.4Automaticky implementované vlastnosti

Automaticky implementovaná vlastnost (nebo automatická vlastnost pro short) je ne abstraktní, non-extern, non-ref-valued vlastnost s pouze středníkem. Automatické vlastnosti musí mít přístupové objekty get a mohou mít volitelně nastavené příslušenství.:

  1. akcesor s tělem obsahujícím pouze středník
  2. použití kontextového klíčového slova field v rámci přístupových objektů nebo textu výrazuvlastnosti

Pokud je vlastnost zadána jako automaticky implementovaná vlastnost, skryté nepojmenované backing pole je automaticky k dispozici pro vlastnost a přístupové objekty jsou implementovány pro čtení a zápis do daného záložního pole. Pro automatické vlastnosti se implementují všechny přístupové objekty get pouze středníky pro čtení a jakýkoli středníkset přístup k zápisu do backingového pole.

Skryté záložní pole je nepřístupné, může být přečteno a zapsáno pouze prostřednictvím automaticky implementovaných přístupových objektů vlastností, a to i v rámci obsahujícího typu.pole backing lze odkazovat přímo pomocí klíčového slova fieldve všech přístupových objektech a v textu výrazu vlastnosti. Protože pole není pojmenováno, nelze ho použít ve výrazunameof.

Pokud má auto-vlastnost žádnou metodu nastavenía pouze get metodu s pouhým středníkem, podkladové pole je považováno za readonly (§15.5.3). Stejně jako pole readonly může být v těle konstruktoru obklopující třídy přiřazena také automatická vlastnost pouze pro čtení (bez přístupového objektu set nebo init). Takové přiřazení přiřadí přímo do vlastnosti, která je pouze pro čtení, do podkladového pole.

Automatické vlastnosti nesmí mít pouze jeden přístupový objekt set středník bez přístupového objektu get.

Automatická vlastnost může mít volitelně property_initializer, který se přímo aplikuje na podkladové pole jako variable_initializer (§17.7).

Následující příklad:

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

je ekvivalentní následující deklaraci:

// 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; } }
}

která odpovídá:

// 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; } }
}

Následující příklad:

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

je ekvivalentní následující deklaraci:

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

Alternativy

Alternativy nulovatelnosti

Kromě přístupu odolnosti vůči null popsaného v části Nullability, pracovní skupina navrhla následující alternativy pro zvážení LDM:

Nic nedělejte

V této chvíli jsme nemohli zavádět žádné zvláštní chování. Platí:

  • Zacházejte s vlastnostmi podporovanými polem stejným způsobem jako s automatickými vlastnostmi, které jsou dnes zpracovávány – musí být inicializovány v konstruktoru, s výjimkou případů, kdy jsou označeny jako povinné, apod.
  • Žádné speciální zacházení s proměnnou pole při analýze přístupových metod majetku. Jedná se jednoduše o proměnnou se stejným typem a nulovou hodnotou jako vlastnost.

Všimněte si, že výsledkem by byla nadbytečná upozornění ve scénářích opožděných vlastností, v takovém případě by uživatelé pravděpodobně museli přiřadit null! nebo podobné, aby umlčeli upozornění konstruktoru.
"Dílčí alternativou", o které můžeme uvažovat, je také zcela ignorovat vlastnosti pomocí klíčového slova field pro analýzu konstruktoru s možnou hodnotou null. V takovém případě by nikde nebyla žádná upozornění o tom, že uživatel potřebuje inicializovat cokoli, ale také žádné nepříjemnosti pro uživatele bez ohledu na to, jaký vzor inicializace může používat.

Vzhledem k tomu, že plánujeme odeslat funkci klíčového slova field pouze v rámci verze Preview LangVersion v .NET 9, očekáváme, že v .NET 10 budeme mít možnost změnit její chování ohledně nulovatelnosti. Proto bychom mohli zvážit přijetí levnějšího řešení, jako je toto, v krátkodobém horizontu a v dlouhodobém horizontu se rozvíjet směrem k jednomu z komplexnějších řešení.

field– cílené atributy nulovatelnosti

Mohli bychom zavést následující výchozí hodnoty, abychom dosáhli přiměřené úrovně bezpečnosti null, aniž bychom museli provádět interproceduralní analýzu vůbec:

  1. Proměnná field má vždy stejnou nulovatelnou anotaci jako vlastnost.
  2. Atributy nulovatelnosti, jako například [field: MaybeNull, AllowNull], lze použít k přizpůsobení nulovatelnosti základního pole.
  3. Vlastnosti založené na polích se kontrolují pro inicializaci v konstruktorech na základě poznámek a atributů s možnou hodnotou null pole.
  4. Settery ve vlastnostech založených na poli kontrolují inicializaci field obdobně jako konstruktory.

To by znamenalo, že "méně aktivní scénář" by vypadal takto:

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();
}

Jedním z důvodů, proč jsme se vyhnuli použití atributů nulovatelnosti, je, že ty, které máme, jsou skutečně zaměřené na popis vstupů a výstupů signatur. Je těžké používat je k popisu nulovatelnosti dlouhodobých proměnných.

  • V praxi je [field: MaybeNull, AllowNull] vyžadováno, aby se pole chovalo rozumně jako proměnná, která může mít hodnotu null. To poskytuje počáteční stav toku, který je možná-prázdný (maybe-null), a umožňuje zapisovat do něj možné hodnoty null. Je to těžkopádné žádat uživatele, aby to udělali, pro běžné "příliš líné" scénáře.
  • Pokud bychom tento přístup provedli, měli bychom zvážit přidání upozornění při použití [field: AllowNull] s návrhem přidat také MaybeNull. Důvodem je to, že AllowNull sám o sobě nedělá to, co uživatelé potřebují z proměnné s možnou hodnotou null: předpokládá, že pole je zpočátku nenulové, když jsme do ní ještě neviděli nic zapisovat.
  • Mohli bychom také zvážit úpravu chování [field: MaybeNull] u klíčového slova field nebo obecně polí, aby bylo možné do proměnné zapisovat také hodnoty null, jako by AllowNull byly implicitně přítomny.

Zodpovězené otázky k LDM

Umístění syntaxe pro klíčová slova

U přístupových objektů, kde by field a value mohly být svázány se syntetizovaným podpůrným polem nebo s implicitním parametrem setteru, ve kterých syntaxových umístěních by měly být identifikátory považovány za klíčová slova?

  1. vždy
  2. pouze primární výrazy
  3. nikdy

První dva případy jsou zásadní změny.

Pokud jsou identifikátory vždy považována za klíčová slova, jedná se o zásadní změnu pro následující příklad:

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

Pokud jsou identifikátory klíčovými slovy pouze při použití jako primární výrazy, změna způsobující chybu je menší. Nejběžnějším přerušením může být neoprávněné použití existujícího člena nazvaného field.

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

Pokud jsou field nebo value předefinovány ve vnořené funkci, dojde také k přerušení. Může to být jediná přestávka pro value pro primární výrazy.

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

Pokud identifikátory nejsou nikdy považovány za klíčová slova, budou svázány pouze s syntetizovaným záložním polem nebo s implicitním parametrem, pokud se neporovnávají s jinými členy. V tomto případě nedošlo k žádné zásadní změně.

Odpověď

field je klíčové slovo v příslušných přístupových objektech, pokud se používá jako primární výraz pouze; value se nikdy nepovažuje za klíčové slovo.

Scénáře podobné { set; }

{ set; } je nyní zakázáno a to dává smysl: pole, které vytváří, nelze nikdy přečíst. Nyní existují nové způsoby, jak se ocitnout v situaci, kdy setter zavádí pomocné pole, které se nikdy nepoužívá, například při rozšíření { set; } na { set => field = value; }.

Který z těchto scénářů by měl být povolený ke kompilaci? Předpokládejme, že upozornění „pole není nikdy čteno“ by platilo stejně jako u ručně deklarovaného pole.

  1. { set; } - Dnes zakázáno, pokračovat v zákazu
  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);
        }
    }
    

Odpověď

Pouze nepovolení toho, co je již zakázáno dnes v automatických vlastnostech, bez těla set;.

field v přístupových objektech událostí

Měl by field být klíčovým slovem v přistupujícím objektu událostí a měl by kompilátor generovat backingové pole?

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

doporučení: field není klíčové slovo v rámci přístupového objektu události a nevygeneruje se žádné backingové pole.

Odpověď

Doporučení bylo přijato. field není klíčové slovo v rámci přístupového objektu události a žádné pole backingu se nevygeneruje.

Nulovatelnost field

Měla by být navrhovaná nulovost field přijata? Podívejte se na část Nullability a otevřenou otázku.

Odpověď

Byl přijat obecný návrh. Konkrétní chování stále potřebuje další kontrolu.

field v inicializátoru vlastností

Měl by field být klíčovým slovem v inicializátoru vlastností a vázat se na podkladové pole?

class A
{
    const int field = -1;

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

Existují užitečné scénáře pro odkazování na záložní pole v inicializátoru?

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

V předchozím příkladu by vazba na záložní pole měla vést k chybě: Inicializátor nemůže odkazovat na nestatické pole.

Odpověď

Inicializátor propojíme jako v předchozích verzích C#. Privátní pole do kontextu nevložíme ani nebudeme bránit odkazování na ostatní členy pojmenované field.

Interakce s částečnými vlastnostmi

Inicializátory

Pokud částečná vlastnost používá field, které části by měly mít inicializátor?

partial class C
{
    public partial int Prop { get; set; } = 1;
    public partial int Prop { get => field; set => field = value; } = 2;
}
  • Zdá se jasné, že když obě části mají inicializátor, měla by nastat chyba.
  • Můžeme si představit případy použití, kdy definice nebo část implementace může chtít nastavit počáteční hodnotu field.
  • Zdá se, že pokud povolíme inicializátor v definiční části, je v podstatě vynuceno, aby implementátor používal field, aby program byl platný. Je to v pořádku?
  • Myslíme si, že pro generátory bude běžné používat field vždy, když je v implementaci potřeba záložní pole stejného typu. Je to částečně proto, že generátory často chtějí uživatelům povolit použití [field: ...] cílových atributů v části definice vlastnosti. Použití klíčového slova field ušetří implementátorovi generátoru námahu s předáváním těchto atributů do vygenerovaného pole a potlačením varování u vlastnosti. Stejné generátory budou pravděpodobně chtít také uživateli povolit zadat počáteční hodnotu pole.

Doporučení: Povolit inicializátor v kterékoli části částečné vlastnosti, pokud implementační část používá field. Oznamte chybu, pokud obě části mají inicializátor.

Odpověď

Doporučení bylo přijato. Deklarování nebo implementace umístění vlastností může použít inicializátor, ale ne obojí současně.

Automatické přístupové objekty

Jak bylo původně navrženo, částečná implementace vlastností musí obsahovat těla pro všechny přístupové objekty. Nedávné iterace funkce klíčového slova field však zahrnovaly pojem "automatické přístupové objekty". Měly by částečné implementace vlastností být schopné používat takové přístupové metody? Pokud se použijí výhradně, bude nerozlišovatelná od definující deklarace.

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.

doporučení: Zakázat automatické přístupové objekty v částečných implementacích vlastností, protože časování jejich použitelnosti je matoucí a nevýhodné oproti výhodám jejich povolení.

Odpověď

Alespoň jedna přístupová metoda musí být ručně implementována, ale druhou přístupovou metodu lze implementovat automaticky.

Pole jen pro čtení

Kdy by se syntetizované backingové pole mělo považovat za jen pro čtení?

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
}

Pokud je backingové pole považováno za jen pro čtení , pole generované do metadat je označeno initonlya pokud field je změněna jinak než v inicializátoru nebo konstruktoru, zobrazí se chyba.

doporučení: Syntetizované backingové pole je jen pro čtení, pokud je typ obsahující struct a vlastnost nebo typ obsahující je deklarován readonly.

Odpověď

Doporučení je přijato.

Kontext jen pro čtení a set

Má být přístupový objekt set povolen v kontextu readonly pro vlastnost, která používá 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?
}

Odpověď

Existují scénáře, kdy implementujete přístupový objekt set do struktury readonly a buď ho předáváte, nebo vyvoláváte. To povolíme.

[Conditional] kód

Má být syntetizované pole generováno, když je field používán jen ve vynechaných voláních podmíněných metod ?

Chcete-li například v sestavení bez ladění vygenerovat záložní pole pro následující pole?

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

Referenční informace najdete v polích parametrů primárního konstruktoru v podobných případech – viz sharplab.io.

doporučení: Záložní pole je vygenerováno, když field je použito pouze ve vynechaných voláních podmíněných metod.

Odpověď

Conditional kód může mít vliv na nepodmíněný kód, například Debug.Assert změnu možnosti null. Bylo by divné, kdyby field neměl podobný dopad. Ve většině kódu se také pravděpodobně nezobrazí, takže uděláme jednoduchou věc a přijmeme doporučení.

Vlastnosti rozhraní a automatické přístupové objekty

Je pro vlastnost interface rozpoznána kombinace ručně a automaticky implementovaných přístupových metod, kde automaticky implementovaný přístup odkazuje na syntetizované podpůrné pole?

U vlastnosti instance se zobrazí chyba, že pole instance nejsou podporována.

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

doporučení: Automatické přístupové metody jsou rozpoznány ve vlastnostech interface a automatické přístupové metody odkazují na syntetizované záložní pole. U vlastnosti instance je hlášena chyba, že pole instance nejsou podporována.

Odpověď

Standardizace toho, že samotné pole instance je příčinou chyby, je v souladu s částečnými vlastnostmi ve třídách a nám se líbí tento výsledek. Doporučení se přijme.