Požadované členy
Poznámka
Tento článek je specifikace funkce. Specifikace slouží jako návrhový dokument pro funkci. Zahrnuje navrhované změny specifikace spolu s informacemi potřebnými při návrhu a vývoji funkce. Tyto články se publikují, dokud nebudou navrhované změny specifikace finalizovány a začleněny do aktuální specifikace ECMA.
Mezi specifikací funkce a dokončenou implementací může docházet k nějakým nesrovnalostem. Tyto rozdíly jsou zachyceny v příslušných poznámkách schůzky návrhu jazyka (LDM).
Další informace o procesu přijetí specifikací funkcí do jazyka C# najdete v článku o specifikacích .
Problém šampiona: https://github.com/dotnet/csharplang/issues/3630
Shrnutí
Tento návrh přidává způsob, jak určit, že vlastnost nebo pole je nutné nastavit během inicializace objektu, což nutí tvůrce instance poskytnout počáteční hodnotu pro člena v inicializátoru objektu na místě vytvoření.
Motivace
Hierarchie objektů dnes vyžadují mnoho šablonového kódu pro přenos dat napříč všemi úrovněmi hierarchie. Podívejme se na jednoduchou hierarchii zahrnující Person
, jak je možné definovat v jazyce C# 8:
class Person
{
public string FirstName { get; }
public string MiddleName { get; }
public string LastName { get; }
public Person(string firstName, string lastName, string? middleName = null)
{
FirstName = firstName;
LastName = lastName;
MiddleName = middleName ?? string.Empty;
}
}
class Student : Person
{
public int ID { get; }
public Student(int id, string firstName, string lastName, string? middleName = null)
: base(firstName, lastName, middleName)
{
ID = id;
}
}
Tady se děje hodně opakování:
- V kořeni hierarchie se musel typ každé vlastnosti opakovat dvakrát a název se musel opakovat čtyřikrát.
- Na odvozené úrovni se typ každé zděděné vlastnosti musel opakovat jednou a název se musel opakovat dvakrát.
Jedná se o jednoduchou hierarchii se 3 vlastnostmi a 1 úrovní dědičnosti, ale mnoho příkladů z reálného světa těchto typů hierarchií prochází mnoha úrovněmi hlouběji, shromádí větší a větší počet vlastností, které se mají předat, jak to dělají. Roslyn je jedním z takových kódových základů, například v různých typech stromů, které tvoří naše CST a AST. Toto vnoření je tak zdlouhavé, že máme generátory kódu pro generování konstruktorů a definic těchto typů, a mnoho zákazníků přistupuje k problému podobným způsobem. C# 9 zavádí záznamy, které mohou v některých scénářích toto zlepšit:
record Person(string FirstName, string LastName, string MiddleName = "");
record Student(int ID, string FirstName, string LastName, string MiddleName = "") : Person(FirstName, LastName, MiddleName);
record
eliminují první zdroj duplikace, ale druhý zdroj duplikace zůstává beze změny: bohužel se jedná o zdroj duplikace, který roste s tím, jak hierarchie roste, a je to nejbolestnější část duplikace, kterou je třeba opravit po provedení změny v hierarchii, protože vyžaduje sledování hierarchie přes všechna její umístění, dokonce i napříč projekty a potenciálně může ovlivnit spotřebitele.
Jako alternativní řešení, abychom se této duplicitě vyhnuli, jsme již dlouho viděli, jak uživatelé využívají inicializátory objektů jako způsob, jak se vyhnout psaní konstruktorů. Před C# 9 to však mělo 2 hlavní nevýhody:
- Hierarchie objektu musí být plně proměnlivá s přístupovými metodami
set
u každé vlastnosti. - Neexistuje způsob, jak zajistit, že každá instance objektu z grafu nastaví všechny své členy.
C# 9 znovu vyřešil první problém, a to zavedením přístupového objektu init
: s ním mohou být tyto vlastnosti nastaveny při vytváření nebo inicializaci objektu, ale ne následně. Stále ale máme druhý problém: vlastnosti v jazyce C# byly od verze C# 1.0 volitelné. Odkazové typy s možnou hodnotou Null, představené v jazyce C# 8.0, řeší část tohoto problému: Pokud konstruktor neinicializuje vlastnost typu odkaz s možnou hodnotou null, zobrazí se uživateli upozornění. Tento problém ale nevyřeší: uživatel zde nechce opakovat velké části svého typu v konstruktoru, chtějí předat požadavek k nastavení vlastností svým příjemcům. Neposkytuje také žádná upozornění týkající se ID
z Student
, protože se jedná o typ hodnoty. Tyto scénáře jsou velmi běžné v ORM databázových modelech, jako je EF Core, které potřebují veřejný konstruktor bez parametrů, ale pak řídí nullovatelnost řádků podle nullovatelnosti vlastností.
Tento návrh se snaží tyto obavy vyřešit zavedením nové funkce jazyka C#: požadovaných členů. Požadované členy budou muset inicializovat příjemci, nikoli autor typu, a to s různými přizpůsobeními, aby bylo možné flexibilně použít více konstruktorů a dalších scénářů.
Podrobný návrh
class
, struct
a typy record
získávají možnost deklarovat required_member_list. Tento seznam je seznam všech vlastností a polí typu, které jsou považovány za požadovanéa musí být inicializovány během vytváření a inicializace instance typu. Typy dědí tyto seznamy z jejich základních typů automaticky a poskytují bezproblémové prostředí, které odstraňuje často používaný a opakující se kód.
modifikátor required
Do seznamu modifikátorů v 'required'
a property_modifierpřidáme .
required_member_list typu se skládá ze všech členů, na které byl required
použit. Proto typ Person
z dřívějších verzí teď vypadá takto:
public class Person
{
// The default constructor requires that FirstName and LastName be set at construction time
public required string FirstName { get; init; }
public string MiddleName { get; init; } = "";
public required string LastName { get; init; }
}
Všechny konstruktory typu, který má required_member_list, automaticky inzerují kontrakt, podle kterého musí příjemci typu inicializovat veškeré vlastnosti ze seznamu. Je chybou, když konstruktor deklaruje smlouvu, která vyžaduje člena, jenž není alespoň tak přístupný jako sám konstruktor. Například:
public class C
{
public required int Prop { get; protected init; }
// Advertises that Prop is required. This is fine, because the constructor is just as accessible as the property initer.
protected C() {}
// Error: ctor C(object) is more accessible than required property Prop.init.
public C(object otherArg) {}
}
required
jsou platné pouze v typech class
, struct
a record
. Není platný v interface
typech.
required
nelze kombinovat s následujícími modifikátory:
fixed
ref readonly
ref
const
static
required
nelze použít u indexerů.
Kompilátor vydá upozornění, když se Obsolete
použije u požadovaného člena typu a:
- Typ není označen
Obsolete
nebo - Žádný konstruktor, který není přiřazen
SetsRequiredMembersAttribute
, není označenObsolete
.
SetsRequiredMembersAttribute
Všechny konstruktory v typu s požadovanými členy, nebo v případě, že jejich základní typ určuje požadované členy, musí mít tyto členy nastaveny uživatelem, když je tento konstruktor volán. Aby bylo možné vyjmout konstruktory z tohoto požadavku, může být konstruktor označen SetsRequiredMembersAttribute
, který tyto požadavky odstraňuje. Tělo konstruktoru není ověřeno, aby se zajistilo, že skutečně nastaví nezbytné členy typu.
SetsRequiredMembersAttribute
z konstruktoru odebere všechny požadavky na a tyto požadavky nejsou žádným způsobem zkontrolovány na platnost. NB: Toto je východisko, pokud je nezbytné dědit z typu se seznamem neplatných požadovaných členů: označte konstruktor tohoto typu pomocí SetsRequiredMembersAttribute
a nebudou hlášeny žádné chyby.
Pokud konstruktor C
řetězí s base
nebo this
konstruktorem, který je označen SetsRequiredMembersAttribute
, C
musí být také označen SetsRequiredMembersAttribute
.
U typů záznamů budeme generovat SetsRequiredMembersAttribute
na syntetizovaném konstruktoru kopírování záznamu, pokud typ záznamu nebo některý z jeho základních typů mají požadované členy.
NB: Starší verze tohoto návrhu měla větší metalanguage kolem inicializace, což umožnilo přidání a odebrání jednotlivých požadovaných členů z konstruktoru a také ověření, že konstruktor nastavoval všechny požadované členy. To se pro počáteční verzi považovalo za příliš složité a odebralo se. Můžeme se podívat na přidání složitějších kontraktů a úprav jako pozdější funkce.
Vynucení
Pro každý konstruktor Ci
typu T
s požadovanými členy R
musí uživatelé volající Ci
udělat jednu z těchto věcí:
- Nastavte všechny členy
R
v object_initializer na object_creation_expression, - Nebo nastavte všechny členy
R
prostřednictvím sekce named_argument_list v attribute_target.
pokud Ci
není přiřazen SetsRequiredMembers
.
Pokud aktuální kontext nepovoluje object_initializer nebo není attribute_targeta Ci
není atributem SetsRequiredMembers
, jedná se o chybu při volání Ci
.
omezení new()
Typ s konstruktorem bez parametrů, který deklaruje kontrakt , nelze nahradit typovým parametrem omezeným na new()
, protože neexistuje způsob, jak může obecná instance zajistit, aby byly splněny požadavky.
struct
default
s
Požadované členy nejsou u instancí typů struct
vytvořených pomocí default
nebo default(StructType)
vynuceny. Vynucují se pro struct
instance vytvořené pomocí new StructType()
, i když StructType
nemá žádný konstruktor bez parametrů a použije se výchozí konstruktor struktury.
Přístupnost
Je chybou označit člena jako povinného, pokud nelze člena nastavit v žádném kontextu, kde je typ viditelný.
- Pokud je člen pole, nemůže být
readonly
. - Pokud je člen vlastností, musí mít setter nebo initer alespoň stejně přístupný jako typ, který člen obsahuje.
To znamená, že následující případy nejsou povolené:
interface I
{
int Prop1 { get; }
}
public class Base
{
public virtual int Prop2 { get; set; }
protected required int _field; // Error: _field is not at least as visible as Base. Open question below about the protected constructor scenario
public required readonly int _field2; // Error: required fields cannot be readonly
protected Base() { }
protected class Inner
{
protected required int PropInner { get; set; } // Error: PropInner cannot be set inside Base or Derived
}
}
public class Derived : Base, I
{
required int I.Prop1 { get; } // Error: explicit interface implementions cannot be required as they cannot be set in an object initializer
public required override int Prop2 { get; set; } // Error: this property is hidden by Derived.Prop2 and cannot be set in an object initializer
public new int Prop2 { get; }
public required int Prop3 { get; } // Error: Required member must have a setter or initer
public required int Prop4 { get; internal set; } // Error: Required member setter must be at least as visible as the constructor of Derived
}
Je chybou skrýt člena required
, protože tento člen už nemůže být nastaven uživatelem.
Při přepsání členu required
musí být klíčové slovo required
součástí signatury metody. Toto se provádí, abychom někdy v budoucnu mohli povolit zrušení požadavku na vlastnost s přepsáním a měli k tomu návrhový prostor.
Přepsání jsou povolena pro označení člena required
, kde nebyl v základním typu označen jako required
. Označený člen je přidán do seznamu požadovaných členů odvozeného typu.
Typy mohou přepsat požadované virtuální vlastnosti. To znamená, že pokud základní virtuální vlastnost má úložiště a odvozený typ se pokusí získat přístup k základní implementaci této vlastnosti, může sledovat neinicializované úložiště. NB: Toto je obecný antivzor jazyka C# a domníváme se, že by se tento návrh neměl snažit jej řešit.
Účinek na analýzu nulových hodnot
Členy označené required
není nutné inicializovat do platného stavu s možnou hodnotou null na konci konstruktoru. Všechny required
členy tohoto typu a všechny základní typy jsou podle analýzy nullable považovány za výchozí na začátku jakéhokoli konstruktoru v tomto typu, pokud nedochází k řetězení na konstruktor this
nebo base
, který má přiřazen atribut SetsRequiredMembersAttribute
.
Analýza nulovatelnosti upozorní na všechny členy required
z aktuálních a základních typů, které nemají platný stav nulovatelnosti na konci konstruktoru opatřeného atributem SetsRequiredMembersAttribute
.
#nullable enable
public class Base
{
public required string Prop1 { get; set; }
public Base() {}
[SetsRequiredMembers]
public Base(int unused) { Prop1 = ""; }
}
public class Derived : Base
{
public required string Prop2 { get; set; }
[SetsRequiredMembers]
public Derived() : base()
{
} // Warning: Prop1 and Prop2 are possibly null.
[SetsRequiredMembers]
public Derived(int unused) : base()
{
Prop1.ToString(); // Warning: possibly null dereference
Prop2.ToString(); // Warning: possibly null dereference
}
[SetsRequiredMembers]
public Derived(int unused, int unused2) : this()
{
Prop1.ToString(); // Ok
Prop2.ToString(); // Ok
}
[SetsRequiredMembers]
public Derived(int unused1, int unused2, int unused3) : base(unused1)
{
Prop1.ToString(); // Ok
Prop2.ToString(); // Warning: possibly null dereference
}
}
Reprezentace metadat
Následující 2 atributy jsou známé kompilátoru jazyka C# a vyžadují, aby tato funkce fungovala:
namespace System.Runtime.CompilerServices
{
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
public sealed class RequiredMemberAttribute : Attribute
{
public RequiredMemberAttribute() {}
}
}
namespace System.Diagnostics.CodeAnalysis
{
[AttributeUsage(AttributeTargets.Constructor, AllowMultiple = false, Inherited = false)]
public sealed class SetsRequiredMembersAttribute : Attribute
{
public SetsRequiredMembersAttribute() {}
}
}
Ruční použití RequiredMemberAttribute
u typu je chyba.
Každý člen označený required
má na sobě aplikován RequiredMemberAttribute
. Kromě toho je každý typ, který definuje takové členy, označen RequiredMemberAttribute
, jako značka označující, že v tomto typu jsou požadované členy. Všimněte si, že pokud typ B
pochází z A
a A
definuje required
členy, ale B
nepřidá žádné nové nebo přepíše žádné existující required
členy, B
nebude označena RequiredMemberAttribute
.
Chcete-li plně určit, zda jsou v B
nějací požadovaní členové, je nutné zkontrolovat celou hierarchii dědičnosti.
Konstruktor v typu s členy required
, na které se SetsRequiredMembersAttribute
nevztahuje, je označen dvěma atributy:
-
System.Runtime.CompilerServices.CompilerFeatureRequiredAttribute
s názvem funkce"RequiredMembers"
. -
System.ObsoleteAttribute
s řetězcovou"Types with required members are not supported in this version of your compiler"
a atribut je označen jako chyba, aby se zabránilo použití těchto konstruktorů u starších kompilátorů.
Nepoužíváme zde modreq
, protože je cílem zachovat binární kompatibilitu: pokud by byla z typu odebrána poslední vlastnost required
, kompilátor by už nesyntetizoval tento modreq
, což je změna narušující binární kompatibilitu a všichni spotřebitelé by museli být rekompilováni. Kompilátor, který rozumí členům required
, tento zastaralý atribut ignoruje. Všimněte si, že členy mohou pocházet také ze základních typů: i když v aktuálním typu nejsou žádné nové required
členy, pokud některý základní typ má required
členy, tento atribut Obsolete
se vygeneruje. Pokud již konstruktor má atribut Obsolete
, nebude generován žádný další atribut Obsolete
.
Používáme ObsoleteAttribute
i CompilerFeatureRequiredAttribute
, protože druhá verze je nová a starší kompilátory tomu nerozumí. V budoucnu možná budeme moct přestat používat ObsoleteAttribute
a nebo ho nebudeme potřebovat k ochraně nových funkcí, ale prozatím potřebujeme obojí pro úplnou ochranu.
Chcete-li vytvořit úplný seznam členů required
R
pro daný typ T
, včetně všech základních typů, spustí se následující algoritmus:
- Pro každou
Tb
počínajeT
a projdete řetězem základních typů, dokud nedosáhneteobject
. - Pokud je
Tb
označenýRequiredMemberAttribute
, pak se všichni členovéTb
označeníRequiredMemberAttribute
shromáždí doRb
- Pro každý
Ri
vRb
, pokud jeRi
přepsán libovolným členemR
, je přeskočen. - V opačném případě, pokud některý
Ri
je skrytý členemR
, vyhledávání požadovaných členů selže a žádné další kroky se neprovedou. Volání libovolného konstruktoruT
, který není přiřazenSetsRequiredMembers
, způsobí chybu. - V opačném případě se
Ri
přidá doR
.
- Pro každý
Otevřené otázky
Inicializátory vnořených členů
Jaké budou mechanismy vynucení pro inicializátory vnořených členů? Budou zcela zakázáni?
class Range
{
public required Location Start { get; init; }
public required Location End { get; init; }
}
class Location
{
public required int Column { get; init; }
public required int Line { get; init; }
}
_ = new Range { Start = { Column = 0, Line = 0 }, End = { Column = 1, Line = 0 } } // Would this be allowed if Location is a struct type?
_ = new Range { Start = new Location { Column = 0, Line = 0 }, End = new Location { Column = 1, Line = 0 } } // Or would this form be necessary instead?
Probírané otázky
Úroveň vynucování pro klauzule init
Funkce klauzule init
nebyla implementována v jazyce C# 11. Zůstává aktivním návrhem.
Vynucujeme striktně, aby členové, kteří jsou zadaní v klauzuli init
bez inicializátoru, inicializovali všechny členy? Zdá se, že to je pravděpodobné, jinak vytvoříme jednoduchou past selhání. Zároveň ale hrozí riziko opětovného zavedení stejných problémů, které jsme vyřešili s MemberNotNull
v jazyce C# 9. Pokud to chceme striktně vynutit, budeme pravděpodobně potřebovat způsob, jak pomocí pomocné metody indikovat, že nastaví člena. Tady je několik možných syntaxí, které jsme probrali:
- Povolit
init
metodu. Tyto metody mohou být volána pouze z konstruktoru nebo z jiné metodyinit
a mají přístup kthis
, jako by byla v konstruktoru (tj. nastavtereadonly
ainit
pole/vlastnosti). To lze kombinovat sinit
klauzulí pro takovéto metody. Klauzuleinit
by byla považována za splněnou, pokud je element v klauzuli jednoznačně přiřazen v těle metody nebo konstruktoru. Volání metody s klauzulíinit
, která zahrnuje člena, se považuje za přiřazení tomuto členu. Pokud jsme se rozhodli, že se jedná o trasu, kterou chceme sledovat, nyní nebo v budoucnu, zdá se pravděpodobné, že bychom neměli používatinit
jako klíčové slovo pro inicializační klauzuli konstruktoru, protože by to bylo matoucí. - Umožňuje operátoru
!
explicitně potlačit upozornění nebo chybu. Pokud inicializujete člena složitým způsobem (například ve sdílené metodě), uživatel může přidat!
do inicializační klauzule, která indikuje, že kompilátor by neměl kontrolovat inicializaci.
závěr: Po diskuzi se nám líbí myšlenka operátoru !
. Umožňuje uživateli postupovat záměrně ve složitějších scénářích, zatímco zároveň nevytváří nejasnosti v návrhu kolem inicializačních metod a nevyžaduje anotovat každou metodu jako nastavující členy X nebo Y. !
jsme zvolili, protože jsme ho již použili k potlačení upozornění na null hodnoty a jeho použití ke sdělení kompilátoru 'jsem chytřejší než ty' je přirozené rozšíření formy syntaxe.
Požadované členy rozhraní
Tento návrh neumožňuje rozhraním označit členy jako povinné. To nás v současné době chrání před tím, abychom museli zjistit složité scénáře týkající se omezení new()
a rozhraní v obecných aplikacích a přímo souvisí s továrnami a obecnou konstrukcí. Abychom zajistili, že v této oblasti máme prostor pro návrh, zakážeme required
v rozhraních a zakážeme, aby byly typy s required_member_lists nahrazeny parametry typu omezenými na new()
. Když se chceme podívat na obecné stavební scénáře s továrnami, můžeme se k tomuto problému vrátit znovu.
Otázky týkající se syntaxe
Funkce klauzule init
nebyla implementována v jazyce C# 11. Zůstává aktivním návrhem.
- Je
init
správné slovo?init
jako příponový modifikátor u konstruktoru může kolidovat, pokud bychom ho někdy chtěli znovu použít pro továrny a zároveň povolit metodyinit
s předponovým modifikátorem. Další možnosti:set
- Je
required
správný modifikátor pro určení inicializace všech členů? Ostatní navrhli:default
all
- S něčím! k označení komplexní logiky
- Měli bychom vyžadovat oddělovač mezi
base
/this
ainit
?- oddělovač
:
- Oddělovač ","
- oddělovač
- Je
required
správný modifikátor? Další navrhované alternativy:req
require
mustinit
must
explicit
závěr: Odebrali jsme prozatím klauzuli konstruktoru init
a pokračujeme s required
jako modifikátorem vlastnosti.
Omezení inicializační klauzule
Funkce klauzule init
nebyla implementována v jazyce C# 11. Zůstává aktivním návrhem.
Měli bychom povolit přístup k this
v klauzuli init? Pokud chceme, aby přiřazení v init
bylo zkratkou pro přiřazení člena v samotném konstruktoru, vypadá to, že bychom to měli.
Vytváří navíc nový rozsah, jako base()
, nebo sdílí stejný rozsah jako tělo metody? To je zvlášť důležité pro věci, jako jsou místní funkce, ke kterým má inicializační klauzule přístup, nebo pro stínování názvů, pokud inicializační výraz zavádí proměnnou prostřednictvím parametru out
.
závěr: byla odstraněna klauzule init
.
Požadavky na přístupnost a init
Funkce klauzule init
nebyla implementována v jazyce C# 11. Zůstává aktivním návrhem.
Ve verzích tohoto návrhu s klauzulí init
jsme mluvili o tom, že můžeme mít následující scénář:
public class Base
{
protected required int _field;
protected Base() {} // Contract required that _field is set
}
public class Derived : Base
{
public Derived() : init(_field = 1) // Contract is fulfilled and _field is removed from the required members list
{
}
}
V tomto okamžiku jsme však z návrhu odebrali klauzuli init
, takže se musíme rozhodnout, zda tento scénář povolit omezeným způsobem. Máme následující možnosti:
- Zakázat scénář. Jedná se o nejkonkonzervativnější přístup a pravidla v přístupnosti jsou v současné době napsaná s tímto předpokladem. Pravidlo je, že každý člen, který je vyžadován, musí být alespoň tak viditelný jako typ, který ho obsahuje.
- Požadovat, aby všechny konstruktory byly buď:
- Není více viditelný než nejméně viditelný požadovaný člen.
- Aplikujte
SetsRequiredMembersAttribute
na konstruktor. To by zajistilo, že každý, kdo vidí konstruktor, může buď nastavit vše, co exportuje, nebo není co nastavovat. To může být užitečné pro typy, které jsou vytvořeny pouze prostřednictvím statickýchCreate
metod nebo podobných sestavovatelů, ale užitečnost se zdá být obecně omezená.
- Přidejte zpět možnost odebrat konkrétní části smlouvy do návrhu, jak bylo dříve projednáno v LDM.
Závěr: Možnost 1, všichni požadovaní členové musí být alespoň tak viditelní jako jejich obsahující typ.
Pravidla přepisování
Aktuální specifikace říká, že klíčové slovo required
je potřeba zkopírovat a že přepsání může učinit člena více povinným, ale ne méně povinným. Je to to, co chceme udělat?
Povolení odebrání požadavků vyžaduje více možností úprav smluv, než aktuálně navrhujeme.
závěr: Přidání required
při přepsání je povoleno. Je-li přepsaný člen required
, pak musí být přepisující člen také required
.
Reprezentace alternativních metadat
Mohli bychom také použít jiný přístup k reprezentaci metadat a inspirovat se rozšiřujícími metodami. Na typ bychom mohli umístit RequiredMemberAttribute
, který označuje, že typ obsahuje požadované členy, a pak na každý požadovaný člen umístit RequiredMemberAttribute
. To by zjednodušilo vyhledávání (nemusíte provádět vyhledávání členů, stačí hledat členy s atributem).
závěr: Alternativní schválení.
Reprezentace metadat
Reprezentace metadat musí být schválena. Dále musíme rozhodnout, jestli mají být tyto atributy zahrnuty do seznamu BCL.
- Pro
RequiredMemberAttribute
je tento atribut spíše podobný obecným integrovaným atributům, které používáme pro názvy členů s možnou hodnotou null, nint nebo tuplů a uživatel ho nebude ručně uplatňovat v C#. Je však možné, že jiné jazyky můžou chtít tento atribut použít ručně. -
SetsRequiredMembersAttribute
, na druhé straně, je přímo používán spotřebiteli, a proto by měl být pravděpodobně v seznamu BCL.
Pokud zvolíme alternativní reprezentaci v předchozí části, která by mohla změnit výpočet ohledně RequiredMemberAttribute
: místo toho, aby se podobala obecným vloženým atributům pro názvy členů nint
/nullable/řazené kolekce, je blíže k System.Runtime.CompilerServices.ExtensionAttribute
, který je součástí frameworku od zavedení rozšiřujících metod.
Závěr: Oba atributy vložíme do BCL.
Upozornění vs. chyba
Nemělo by nezadání povinného člena být upozorněním nebo chybou? Je jistě možné systém zkomplikovat prostřednictvím Activator.CreateInstance(typeof(C))
nebo podobné, což znamená, že nemusíme být schopni plně zaručit, že všechny vlastnosti jsou vždy nastaveny. Umožňujeme také potlačení diagnostiky na místě konstruktoru pomocí !
, což pro chyby obecně nepovolujeme. Funkce je však podobná polím readonly nebo vlastnostem init v tom, že pokud se uživatelé pokusí nastavit takového člena po inicializaci, vyvolá to přísnou chybu. Tato omezení však lze obejít pomocí reflexe.
závěr: Chyby.
C# feature specifications