Delen via


Vereiste leden

Notitie

Dit artikel is een functiespecificatie. De specificatie fungeert als het ontwerpdocument voor de functie. Het bevat voorgestelde specificatiewijzigingen, samen met informatie die nodig is tijdens het ontwerp en de ontwikkeling van de functie. Deze artikelen worden gepubliceerd totdat de voorgestelde specificaties zijn voltooid en opgenomen in de huidige ECMA-specificatie.

Er kunnen enkele verschillen zijn tussen de functiespecificatie en de voltooide implementatie. Deze verschillen worden vastgelegd in de relevante LDM-notities (Language Design Meeting).

Meer informatie over het proces voor het aannemen van functiespeclets in de C#-taalstandaard vindt u in het artikel over de specificaties.

Samenvatting

Dit voorstel voegt een manier toe om op te geven dat een eigenschap of veld moet worden ingesteld tijdens de object-initialisatie, waardoor de maker van het exemplaar een initiële waarde moet opgeven voor het onderdeel bij het creëren van het object.

Motivatie

Objecthiërarchieën vereisen tegenwoordig veel standaardgegevens voor alle niveaus van de hiërarchie. Laten we eens kijken naar een eenvoudige hiërarchie met een Person zoals kan worden gedefinieerd in 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;
    }
}

Er is hier veel herhaling gaande:

  1. Aan de basis van de hiërarchie moest het type van elke eigenschap tweemaal en de naam viermaal worden herhaald.
  2. Op het afgeleide niveau moest het type van elke overgenomen eigenschap eenmaal worden herhaald en moest de naam tweemaal worden herhaald.

Dit is een eenvoudige hiërarchie met 3 eigenschappen en 1 niveau van overname, maar veel praktijkvoorbeelden van deze typen hiërarchieën gaan veel dieper, verzamelen grotere en grotere aantallen eigenschappen om door te geven zoals ze dat doen. Roslyn is een dergelijke codebasis, bijvoorbeeld in de verschillende boomtypen die onze CST's en AST's maken. Deze nesting is zo vervelend dat we codegeneratoren hebben om constructors en definities van deze typen te genereren, en veel klanten hanteren soortgelijke benaderingen voor het probleem. C# 9 introduceert records, die voor sommige scenario's dit beter kunnen maken:

record Person(string FirstName, string LastName, string MiddleName = "");
record Student(int ID, string FirstName, string LastName, string MiddleName = "") : Person(FirstName, LastName, MiddleName);

recordelimineren de eerste bron van duplicatie, maar de tweede bron van duplicatie blijft ongewijzigd: helaas is dit de bron van duplicatie die toeneemt naarmate de hiërarchie groeit en het pijnlijkste deel van de duplicatie is om op te lossen na het aanbrengen van een wijziging in de hiërarchie, omdat het nodig is om de hiërarchie door al haar locaties te moeten volgen, mogelijk zelfs projecten overstijgend en consumenten verstorend werken.

Als tijdelijke oplossing om deze duplicatie te voorkomen, zien we al lange tijd dat consumenten objectinitializers omarmen als een manier om het schrijven van constructors te vermijden. Vóór C# 9 had dit echter twee belangrijke nadelen:

  1. De objecthiërarchie moet volledig veranderlijk zijn, met set accessors op elke eigenschap.
  2. Er is geen manier om ervoor te zorgen dat elke instantie van een object uit de grafiek elk lid instelt.

C# 9 heeft het eerste probleem hier opnieuw opgelost, door de init accessor te introduceren: hiermee kunnen deze eigenschappen worden ingesteld voor het maken/initialiseren van objecten, maar niet vervolgens. We hebben echter nog steeds het tweede probleem: eigenschappen in C# zijn optioneel sinds C# 1.0. Nulleerbare verwijzingstypen, geïntroduceerd in C# 8.0, hebben een deel van dit probleem opgelost: als een constructor een niet-nulleerbare verwijzingstype-eigenschap niet initialiseert, wordt de gebruiker hierover gewaarschuwd. Dit lost het probleem echter niet op: de gebruiker hier wil niet grote delen van hun type in de constructor herhalen, ze willen de -vereiste doorgeven om eigenschappen aan hun consumenten mee te geven. Het biedt ook geen waarschuwingen over ID van Student, omdat dat een waardetype is. Deze scenario's komen zeer vaak voor in databasemodel-ORM's, zoals EF Core, die een openbare parameterloze constructor moeten hebben, maar vervolgens de nullabiliteit van de rijen moeten afleiden op basis van de nullabiliteit van de eigenschappen.

Dit voorstel probeert deze zorgen te verhelpen door een nieuwe functie voor C# te introduceren: vereiste leden. Vereiste leden moeten worden geïnitialiseerd door consumenten, in plaats van door de auteur van het type, met verschillende aanpassingen om flexibiliteit voor meerdere constructors en andere scenario's mogelijk te maken.

Gedetailleerd ontwerp

class, struct, en record-typen krijgen de mogelijkheid om een -verplichte_ledenlijstte declareren. Deze lijst is de lijst met alle eigenschappen en velden van een type die worden beschouwd als vereisten die moeten worden geïnitialiseerd tijdens de bouw en initialisatie van een exemplaar van het type. Typen nemen deze lijsten automatisch over van hun basistypen, waardoor een naadloze ervaring wordt geboden waarmee standaard- en terugkerende code wordt verwijderd.

required modifier

We voegen 'required' toe aan de lijst met modifiers in field_modifier en property_modifier. De required_member_list van een type bestaat uit alle leden waarop required zijn toegepast. Het Person type van eerder ziet er dus als volgt uit:

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

Alle constructors voor een type met een required_member_list gaan automatisch een contract aan dat consumenten van het type alle eigenschappen in de lijst moeten initialiseren. Het is een fout voor een constructor om een contract te adverteren waarvoor een lid is vereist dat niet ten minste zo toegankelijk is als de constructor zelf. Bijvoorbeeld:

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 is alleen geldig in class, structen record typen. Het is niet geldig in interface types. required kan niet worden gecombineerd met de volgende modifiers:

  • fixed
  • ref readonly
  • ref
  • const
  • static

required mag niet worden toegepast op indexeerfuncties.

De compiler geeft een waarschuwing wanneer Obsolete wordt toegepast op een vereist lid van een type en:

  1. Het type is niet gemarkeerd Obsoleteof
  2. Een constructor die niet gekoppeld is aan SetsRequiredMembersAttribute is niet gemarkeerd als Obsolete.

SetsRequiredMembersAttribute

Alle constructors in een type met vereiste leden, of waarvan het basistype vereiste leden aangeeft, moeten door een gebruiker worden ingesteld wanneer die constructor wordt aangeroepen. Om constructors van deze vereiste te kunnen uitsluiten, kan een constructor worden toegeschreven aan SetsRequiredMembersAttribute, waardoor deze vereisten worden verwijderd. De hoofdtekst van de constructor wordt niet gevalideerd om ervoor te zorgen dat de vereiste leden van het type zeker worden ingesteld.

SetsRequiredMembersAttribute verwijdert alle vereisten van een constructor en deze vereisten worden op geen enkele manier gecontroleerd op geldigheid. NB: dit is de uitweg als het overnemen van een type met een ongeldige verplichte ledenlijst nodig is: markeer de constructor van dat type met SetsRequiredMembersAttribute, en er zullen geen fouten worden gerapporteerd.

Als een constructor C ketent aan een base- of this-constructor die de eigenschap SetsRequiredMembersAttributeheeft, moet C ook de eigenschap SetsRequiredMembersAttributehebben.

Voor recordtypen geven we SetsRequiredMembersAttribute uit op de gesynthetiseerde copyconstructor van een record als het recordtype of een van de basistypen vereiste leden heeft.

NB: Een eerdere versie van dit voorstel had een grotere metalanguage rond initialisatie, waardoor afzonderlijke vereiste leden van een constructor kunnen worden toegevoegd en verwijderd, evenals de validatie dat de constructor alle vereiste leden heeft ingesteld. Dit werd te complex geacht voor de eerste release en verwijderd. We kunnen kijken naar het toevoegen van complexere contracten en wijzigingen als een latere functie.

Handhaving

Voor elke constructor Ci in het type T met de vereiste leden R, moeten consumenten die Ci aanroepen een van de volgende handelingen uitvoeren:

  • Alle leden van R instellen in een object_initializer op de object_creation_expression,
  • Of stel alle leden van R in via de sectie named_argument_list van een attribute_target.

tenzij Ci wordt toegeschreven aan SetsRequiredMembers.

Als de huidige context geen object_initializer toestaat of geen attribute_targetis en Ci niet is toegeschreven aan SetsRequiredMembers, is het een fout om Ciaan te roepen.

new() beperking

Een type met een parameterloze constructor waarmee een contract wordt aangekondigd mag niet worden vervangen door een typeparameter die is beperkt tot new(), omdat er geen manier is voor de algemene instantiëring om ervoor te zorgen dat aan de vereisten wordt voldaan.

struct defaults

Vereiste leden worden niet afgedwongen voor exemplaren van struct-typen die met default of default(StructType)zijn gemaakt. Ze worden afgedwongen voor struct exemplaren die zijn gemaakt met new StructType(), zelfs wanneer StructType geen parameterloze constructor heeft en de standaardstruct-constructor wordt gebruikt.

Toegankelijkheid

Het is een fout om een lid als vereist te markeren als het lid niet kan worden ingesteld in een context waarin het type zichtbaar is.

  • Als het lid een veld is, kan het geen readonlyzijn.
  • Als het lid een eigenschap is, moet het een setter of initer hebben die minstens zo toegankelijk is als het type dat het lid bevat.

Dit betekent dat de volgende gevallen niet zijn toegestaan:

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
}

Het is een fout om een required lid te verbergen, omdat dat lid niet meer door een consument kan worden ingesteld.

Wanneer u een required lid overschrijft, moet het trefwoord required worden opgenomen in de handtekening van de methode. Dit wordt gedaan zodat we, als we in de toekomst ooit willen toestaan dat het niet meer vereisen van een eigenschap wordt overschreven, hiervoor ontwerpruimte hebben.

Overschrijvingen kunnen een lid markeren required waar het niet was required in de basisklasse. Een lid dat zo is gemarkeerd, wordt toegevoegd aan de lijst met vereiste leden van het afgeleide type.

Typen mogen vereiste virtuele eigenschappen overschrijven. Dit betekent dat als de virtuele basiseigenschap opslag heeft en het afgeleide type toegang probeert te krijgen tot de basisimplementatie van die eigenschap, ze niet-geïnitialiseerde opslag kunnen observeren. NB: Dit is een algemeen C#-antipatroon en we denken niet dat dit voorstel dat zou moeten proberen aan te pakken.

Effect op null-analyse

Leden die als required zijn gemarkeerd, hoeven niet te worden geïnitialiseerd tot een geldige null-status aan het einde van een constructor. Alle required-leden van dit type en eventuele basistypen worden door nullanalyse als standaardwaarde beschouwd aan het begin van een constructor in dat type, tenzij er wordt gekoppeld aan een this- of base-constructor die is toegeschreven aan SetsRequiredMembersAttribute.

Een null-analyse waarschuwt alle required leden van de huidige en basistypen die geen geldige null-status hebben aan het einde van een constructor die is toegeschreven aan 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
    }
}

Weergave van metagegevens

De volgende 2 kenmerken zijn bekend bij de C#-compiler en zijn vereist om deze functie te laten functioneren:

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

Het is een fout om handmatig RequiredMemberAttribute toe te passen op een type.

Elk lid dat gemarkeerd is als required, heeft een RequiredMemberAttribute daarop toegepast. Bovendien wordt elk type dat dergelijke leden definieert gemarkeerd met RequiredMemberAttribute, als markering om aan te geven dat er vereiste leden in dit type zijn. Als het type B is afgeleid van Aen Arequired leden definieert, maar B geen nieuwe leden toevoegt of bestaande required leden overschrijft, wordt B niet gemarkeerd met een RequiredMemberAttribute. Om volledig te bepalen of er vereiste leden zijn in B, is het controleren van de volledige overnamehiërarchie nodig.

Elke constructor in een type met required leden waarop geen SetsRequiredMembersAttribute is toegepast, wordt gemarkeerd met twee kenmerken:

  1. System.Runtime.CompilerServices.CompilerFeatureRequiredAttribute met de functienaam "RequiredMembers".
  2. System.ObsoleteAttribute met de tekenreeks "Types with required members are not supported in this version of your compiler"en het kenmerk wordt gemarkeerd als een fout, om te voorkomen dat oudere compilers deze constructors gebruiken.

We gebruiken hier geen modreq omdat het doel is om binaire compatibiliteit te behouden: als de laatste required-eigenschap uit een type is verwijderd, zou de compiler deze modreqniet meer synthetiseren, wat een binary-brekende verandering is en alle gebruikers hun code opnieuw moeten compileren. Een compiler die required-leden begrijpt, zal dit verouderde kenmerk negeren. Houd er rekening mee dat leden ook afkomstig kunnen zijn van basistypen: zelfs als er geen nieuwe required leden in het huidige type zijn, wordt dit required kenmerk gegenereerd als een basistype Obsolete leden heeft. Als de constructor al een Obsolete kenmerk heeft, wordt er geen extra Obsolete kenmerk gegenereerd.

We gebruiken zowel ObsoleteAttribute als CompilerFeatureRequiredAttribute omdat de laatste nieuwe release is en oudere compilers het niet begrijpen. In de toekomst kunnen we de ObsoleteAttribute laten vallen en/of het niet gebruiken om nieuwe functies te beschermen, maar voorlopig hebben we ze allebei nodig voor volledige bescherming.

Het volgende algoritme wordt uitgevoerd om de volledige lijst met required leden te maken R voor een bepaald type T, inclusief alle basistypen:

  1. Voor elke Tbbegint u met T en doorloopt u de basistypeketen totdat object is bereikt.
  2. Als Tb is gemarkeerd met RequiredMemberAttribute, worden alle leden van Tb die zijn gemarkeerd met RequiredMemberAttribute verzameld in Rb
    1. Voor elke Ri in Rb, als Ri wordt overschreven door een lid van R, wordt deze overgeslagen.
    2. Als een Ri wordt verborgen door een lid van R, mislukt het opzoeken van de vereiste leden en worden er geen verdere stappen ondernomen. Als u een constructor van T aanroept die niet is toegeschreven aan SetsRequiredMembers treedt er een fout op.
    3. Anders wordt Ri toegevoegd aan R.

Openstaande vragen

Geneste initialisatie van leden

Wat zijn de afdwingingsmechanismen voor geneste ledeninitialisaties? Zullen ze helemaal niet worden toegestaan?

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?

Besproken vragen

Afdwingingsniveau voor init-clausules

De init componentfunctie is niet geïmplementeerd in C# 11. Het blijft een actief voorstel.

Hanteren we strikt dat leden die zijn opgegeven in een init-clausule zonder initializer alle leden moeten initialiseren? Het lijkt waarschijnlijk dat we dat zullen doen, anders creëren we een eenvoudige valkuil voor mislukking. We lopen echter ook het risico om dezelfde problemen die we met MemberNotNull in C# 9 hebben opgelost, opnieuw in te voeren. Als we dit strikt willen afdwingen, hebben we waarschijnlijk een manier nodig voor een helpermethode om aan te geven dat er een lid wordt ingesteld. Enkele mogelijke syntaxis die we hiervoor hebben besproken:

  • init methoden toestaan. Deze methoden mogen alleen worden aangeroepen vanuit een constructor of vanuit een andere init methode en hebben toegang tot this alsof deze zich in de constructor bevindt (bijvoorbeeld readonly en init velden/eigenschappen). Dit kan worden gecombineerd met init clausules voor dergelijke methoden. Een init-clausule wordt als voldaan beschouwd als het lid in de clausule zeker is toegekend in de hoofdtekst van de methode/constructor. Het aanroepen van een methode met een init-clausule die een lid bevat, geldt als toewijzen aan dat lid. Als we besluiten dat dit een route is die we nu of in de toekomst willen volgen, lijkt het erop dat we waarschijnlijk niet init moeten gebruiken als het trefwoord voor de init-component voor een constructor, omdat dat verwarrend zou zijn.
  • Laat de operator ! expliciet de waarschuwing/fout onderdrukken. Als het initialiseren van een lid op een complexe manier gebeurt, zoals bijvoorbeeld in een gedeelde methode, kan de gebruiker een ! toevoegen aan de init-clausule om aan te geven dat de compiler niet op initialisatie moet controleren.

Conclusie: Na discussie vinden we het idee van de operator ! leuk. Hiermee kan de gebruiker bewust omgaan met complexere scenario's, terwijl er ook geen groot ontwerpgat rond init-methoden wordt gemaakt en elke methode wordt gemarkeerd bij het instellen van leden X of Y. ! is gekozen omdat we deze al gebruiken voor het onderdrukken van nullable waarschuwingen en het gebruik hiervan om de compiler op een andere plaats te vertellen "Ik ben slimmer dan jij", is een natuurlijke uitbreiding van de syntaxisvorm.

Vereiste interfaceleden

Dit voorstel staat niet toe dat interfaces leden markeren als vereist. Dit beschermt ons tegen het achterhalen van complexe scenario's rond new() en interfacebeperkingen in generieken, en is direct gerelateerd aan zowel fabrieken als algemene constructie. Om ervoor te zorgen dat we ontwerpruimte op dit gebied hebben, verbieden we required in interfaces en verbieden we dat typen met required_member_lists worden vervangen door typeparameters die beperkt zijn door new(). Als we algemene bouwscenario's met fabrieken willen bekijken, kunnen we dit probleem opnieuw bekijken.

Syntaxisvragen

De init componentfunctie is niet geïmplementeerd in C# 11. Het blijft een actief voorstel.

  • Is init het juiste woord? init als een postfix modifier voor de constructor kan interfereren als we deze ooit opnieuw willen gebruiken voor factories en ook init methoden met een prefix modifier willen inschakelen. Andere mogelijkheden:
    • set
  • Is required de juiste modifier om aan te geven dat alle leden zijn geïnitialiseerd? Anderen stelden voor:
    • default
    • all
    • Met een ! om complexe logica aan te geven
  • Moeten we een scheidingsteken tussen de base/this en de initvereisen?
    • scheider :
    • ',' scheidingsteken
  • Is required de juiste modifier? Andere alternatieven die zijn voorgesteld:
    • req
    • require
    • mustinit
    • must
    • explicit

Conclusie: we hebben de init constructorcomponent voorlopig verwijderd en gaan door met required als eigenschapsaanpassing.

Beperkingen voor Init-clausules

De init componentfunctie is niet geïmplementeerd in C# 11. Het blijft een actief voorstel.

Moeten we toegang tot this in de init-component toestaan? Als we willen dat de toewijzing in init een afkorting is voor het toewijzen van het lid in de constructor zelf, lijkt het alsof we dat moeten doen.

2. Maakt het ook een nieuw bereik, zoals base() dat doet, of deelt het dezelfde scope als de hoofdtekst van de methode? Dit is met name belangrijk voor zaken zoals lokale functies, waartoe de init-component mogelijk toegang wil hebben, of voor naamschaduwing, als een init-expressie een variabele introduceert via out parameter.

Conclusie: init component is verwijderd.

Toegankelijkheidsvereisten en init

De init componentfunctie is niet geïmplementeerd in C# 11. Het blijft een actief voorstel.

In versies van dit voorstel met de init-clausule spraken we over de mogelijkheid van het volgende scenario:

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

We hebben echter de init clausule van het voorstel op dit moment verwijderd, dus we moeten beslissen of dit scenario op beperkte wijze moet worden toegestaan. De opties die we hebben, zijn:

  1. Sta het scenario niet toe. Dit is de meest conservatieve benadering en de regels in de Toegankelijkheid zijn momenteel geschreven met deze aanname. De regel is dat elk lid dat vereist is, ten minste zo zichtbaar moet zijn als het bijbehorende type.
  2. Vereisen dat alle constructors aan een van de volgende voorwaarden voldoen:
    1. Niet meer zichtbaar dan het minst zichtbare vereiste lid.
    2. Zorg ervoor dat SetsRequiredMembersAttribute op de constructor wordt toegepast. Dit zou ervoor zorgen dat iedereen die een constructor kan zien, alles kan instellen wat het exporteert, of dat er niets hoeft te worden ingesteld. Dit kan handig zijn voor typen die alleen worden gemaakt via statische Create methoden of vergelijkbare opbouwfuncties, maar het hulpprogramma lijkt over het algemeen beperkt.
  3. Voeg een manier toe om specifieke onderdelen van het contract aan het voorstel te verwijderen, zoals eerder besproken in LDM.

Conclusie: Bij optie 1 moeten alle vereiste leden minstens zo zichtbaar zijn als hun type.

Regels overschrijven

De huidige specificatie geeft aan dat het required trefwoord moet worden gekopieerd en dat overschrijvingen een lid meer vereist kunnen maken, maar niet minder. Is dat wat we willen doen? Het toestaan van het verwijderen van vereisten vereist meer mogelijkheden voor contractaanpassing dan we momenteel voorstellen.

Conclusie: Het toevoegen van required bij overschrijven is toegestaan. Als het overschreven lid requiredis, moet het overschrijvend lid ook requiredzijn.

Alternatieve metagegevensweergave

We kunnen ook kiezen voor een andere benadering van de weergave van metagegevens, waarbij we ons laten inspireren door extensiemethoden. We kunnen een RequiredMemberAttribute op het type plaatsen om aan te geven dat het type vereiste leden bevat en vervolgens een RequiredMemberAttribute op elk lid plaatsen dat vereist is. Dit vereenvoudigt de opzoekvolgorde (u hoeft geen lidzoekactie uit te voeren, alleen leden met het kenmerk te zoeken).

Conclusie: Alternatief goedgekeurd.

Weergave van metagegevens

De metagegevensweergave moet worden goedgekeurd. Daarnaast moeten we bepalen of deze kenmerken moeten worden opgenomen in de BCL.

  1. Voor RequiredMemberAttributeis dit kenmerk meer verwant aan de algemene ingesloten kenmerken die we gebruiken voor namen van nullable/nint/tuple-leden en worden deze niet handmatig toegepast door de gebruiker in C#. Het is echter mogelijk dat andere talen dit kenmerk handmatig willen toepassen.
  2. SetsRequiredMembersAttributedaarentegen rechtstreeks door consumenten wordt gebruikt en dus waarschijnlijk in de BCL zou moeten staan.

Als we de alternatieve weergave in de vorige sectie gebruiken, kan dat de calculus op RequiredMemberAttributewijzigen: in plaats van vergelijkbaar te zijn met de algemene ingesloten kenmerken voor nint/nullable/tuple-lidnamen, ligt het dichter bij System.Runtime.CompilerServices.ExtensionAttribute, die in het framework is opgenomen sinds de uitbreidingsmethoden zijn verzonden.

Conclusie: We zullen beide kenmerken in de BCL plaatsen.

Waarschuwing versus fout

Zou het niet instellen van een verplicht lid een waarschuwing of een fout moeten zijn? Het is zeker mogelijk om het systeem te misleiden, via Activator.CreateInstance(typeof(C)) of vergelijkbaar, wat betekent dat we mogelijk niet volledig kunnen garanderen dat alle eigenschappen altijd zijn ingesteld. We staan ook onderdrukking van de diagnostische gegevens op de constructor-site toe met behulp van de !, die we over het algemeen niet toestaan voor fouten. De functie is echter vergelijkbaar met alleen-lezen velden of init-eigenschappen, omdat er een ernstige fout optreedt als gebruikers proberen een dergelijk lid te wijzigen na de initialisatie, maar ze kunnen worden omzeild door middel van reflectie.

Conclusie: Fouten.