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;
ainit;
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;
neboinit;
).
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:
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).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 slovafield
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
field
má anotovanou nulovatelnost, např.string?
. To způsobí, žefield
může mít možná-nulový nebo mít možná-výchozí počáteční stav v přistupovačiget
, 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
,NotNull
neboDisallowNull
, 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
neboT
) 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
neboT
) 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
,set
ainit
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í.:
- akcesor s tělem obsahujícím pouze středník
- použití kontextového klíčového slova
field
v rámci přístupových objektů nebo textu výrazuvlastnostiPokud 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é objektyget
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 slovafield
ve 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 zareadonly
(§15.5.3). Stejně jako polereadonly
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 dovlastnosti, 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 objektuget
.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:
- Proměnná
field
má vždy stejnou nulovatelnou anotaci jako vlastnost. - Atributy nulovatelnosti, jako například
[field: MaybeNull, AllowNull]
, lze použít k přizpůsobení nulovatelnosti základního pole. - 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.
- 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 slovafield
nebo obecně polí, aby bylo možné do proměnné zapisovat také hodnoty null, jako byAllowNull
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?
- vždy
- pouze primární výrazy
- 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.
-
{ set; }
- Dnes zakázáno, pokračovat v zákazu { 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); } }
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 slovafield
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 initonly
a 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.
C# feature specifications