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:
- Aan de basis van de hiërarchie moest het type van elke eigenschap tweemaal en de naam viermaal worden herhaald.
- 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);
record
elimineren 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:
- De objecthiërarchie moet volledig veranderlijk zijn, met
set
accessors op elke eigenschap. - 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
, struct
en 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:
- Het type is niet gemarkeerd
Obsolete
of - Een constructor die niet gekoppeld is aan
SetsRequiredMembersAttribute
is niet gemarkeerd alsObsolete
.
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 SetsRequiredMembersAttribute
heeft, moet C
ook de eigenschap SetsRequiredMembersAttribute
hebben.
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 Ci
aan 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
default
s
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
readonly
zijn. - 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 A
en A
required
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:
-
System.Runtime.CompilerServices.CompilerFeatureRequiredAttribute
met de functienaam"RequiredMembers"
. -
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 modreq
niet 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:
- Voor elke
Tb
begint u metT
en doorloopt u de basistypeketen totdatobject
is bereikt. - Als
Tb
is gemarkeerd metRequiredMemberAttribute
, worden alle leden vanTb
die zijn gemarkeerd metRequiredMemberAttribute
verzameld inRb
- Voor elke
Ri
inRb
, alsRi
wordt overschreven door een lid vanR
, wordt deze overgeslagen. - Als een
Ri
wordt verborgen door een lid vanR
, mislukt het opzoeken van de vereiste leden en worden er geen verdere stappen ondernomen. Als u een constructor vanT
aanroept die niet is toegeschreven aanSetsRequiredMembers
treedt er een fout op. - Anders wordt
Ri
toegevoegd aanR
.
- Voor elke
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 andereinit
methode en hebben toegang totthis
alsof deze zich in de constructor bevindt (bijvoorbeeldreadonly
eninit
velden/eigenschappen). Dit kan worden gecombineerd metinit
clausules voor dergelijke methoden. Eeninit
-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 eeninit
-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 nietinit
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 ookinit
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 deinit
vereisen?- scheider
:
- ',' scheidingsteken
- scheider
- 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:
- 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.
- Vereisen dat alle constructors aan een van de volgende voorwaarden voldoen:
- Niet meer zichtbaar dan het minst zichtbare vereiste lid.
- 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 statischeCreate
methoden of vergelijkbare opbouwfuncties, maar het hulpprogramma lijkt over het algemeen beperkt.
- 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 required
is, moet het overschrijvend lid ook required
zijn.
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.
- Voor
RequiredMemberAttribute
is 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. -
SetsRequiredMembersAttribute
daarentegen 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 RequiredMemberAttribute
wijzigen: 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.
C# feature specifications