Sdílet prostřednictvím


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í:

  1. V kořeni hierarchie se musel typ každé vlastnosti opakovat dvakrát a název se musel opakovat čtyřikrát.
  2. 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);

recordeliminují 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:

  1. Hierarchie objektu musí být plně proměnlivá s přístupovými metodami set u každé vlastnosti.
  2. 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, structa 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, structa 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:

  1. Typ není označen Obsoletenebo
  2. Žádný konstruktor, který není přiřazen SetsRequiredMembersAttribute, není označen Obsolete.

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í SetsRequiredMembersAttributea 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 Rmusí 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 defaults

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 Aa 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 Bně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:

  1. System.Runtime.CompilerServices.CompilerFeatureRequiredAttribute s názvem funkce "RequiredMembers".
  2. 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ů requiredR pro daný typ T, včetně všech základních typů, spustí se následující algoritmus:

  1. Pro každou Tbpočínaje T a projdete řetězem základních typů, dokud nedosáhnete object.
  2. Pokud je Tb označený RequiredMemberAttribute, pak se všichni členové Tb označení RequiredMemberAttribute shromáždí do Rb
    1. Pro každý Ri v Rb, pokud je Ri přepsán libovolným členem R, je přeskočen.
    2. V opačném případě, pokud některý Ri je skrytý členem R, vyhledávání požadovaných členů selže a žádné další kroky se neprovedou. Volání libovolného konstruktoru T, který není přiřazen SetsRequiredMembers, způsobí chybu.
    3. V opačném případě se Ri přidá do R.

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é metody init a mají přístup k this, jako by byla v konstruktoru (tj. nastavte readonly a init pole/vlastnosti). To lze kombinovat s init klauzulí pro takovéto metody. Klauzule init 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žívat init 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 metody init 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 a init?
    • 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:

  1. 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.
  2. Požadovat, aby všechny konstruktory byly buď:
    1. Není více viditelný než nejméně viditelný požadovaný člen.
    2. 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ých Create metod nebo podobných sestavovatelů, ale užitečnost se zdá být obecně omezená.
  3. 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.

  1. Pro RequiredMemberAttributeje 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ě.
  2. 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.