Structs opnemen
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.
Kampioensprobleem: https://github.com/dotnet/csharplang/issues/4334
De syntaxis voor een recordstruct is als volgt:
record_struct_declaration
: attributes? struct_modifier* 'partial'? 'record' 'struct' identifier type_parameter_list?
parameter_list? struct_interfaces? type_parameter_constraints_clause* record_struct_body
;
record_struct_body
: struct_body
| ';'
;
Recordstructtypen zijn waardetypen, zoals andere structtypen. Ze nemen impliciet over van de klasse System.ValueType
.
De modifiers en leden van een recordstruct zijn onderworpen aan dezelfde beperkingen als die van structs (toegankelijkheid op type, modifiers op leden, base(...)
instantieconstructorinitializers, definitieve toewijzing voor this
in constructor, destructors, ...). Recordstructs volgen ook dezelfde regels als structs voor parametersloze instantieconstructors en veldinitialiseringen, maar in dit document wordt aangenomen dat deze beperkingen voor structs in het algemeen worden versoepeld.
Zie §16.4.9 Zie parameterloze struct constructors spec.
Recordstructs kunnen geen ref
modifier gebruiken.
Ten hoogste één gedeeltelijke typedeclaratie van een gedeeltelijke recordstruct kan een parameter_list
bieden.
De parameter_list
is mogelijk leeg.
Recordstructparameters kunnen geen ref
, out
of this
modifiers gebruiken (maar in
en params
zijn toegestaan).
Leden van een recordstructuur
Naast de leden die zijn gedeclareerd in de recordstructbody, heeft een recordstructtype extra gesynthetiseerde leden. Leden worden gesynthetiseerd tenzij een lid met een 'overeenkomende' handtekening wordt gedeclareerd in de hoofdtekst van de recordstruct of een toegankelijk concreet niet-virtueel lid met een 'overeenkomende' handtekening wordt overgenomen. Twee leden worden beschouwd als overeenkomend als ze dezelfde signatuur hebben of als ze in een overervingssituatie als verbergend worden gezien. Zie handtekeningen en overbelasting §7.6. Het is een fout dat een lid van een recordstruct de naam 'Clone' krijgt.
Het is fout als een instanceveld van een recordstruct een onveilig type heeft.
Een recordstruct mag geen destructor declareren.
De gesynthetiseerde leden zijn als volgt:
Leden voor gelijkheid
De gesynthetiseerde gelijkheidsleden zijn vergelijkbaar zoals in een recordklasse (Equals
voor dit type, Equals
voor het object
-type, ==
en !=
operatoren voor dit type).
met uitzondering van het ontbreken van EqualityContract
, null-controles of overname.
De recordstruct implementeert System.IEquatable<R>
en bevat een gesynthetiseerde sterk getypeerde overbelasting van Equals(R other)
waarbij R
de recordstruct is.
De methode is public
.
De methode kan expliciet worden gedeclareerd. Dit is een fout als de expliciete declaratie niet overeenkomt met de verwachte handtekening of toegankelijkheid.
Als Equals(R other)
door de gebruiker gedefinieerd (niet gesynthetiseerd) maar GetHashCode
niet is, wordt er een waarschuwing gegenereerd.
public readonly bool Equals(R other);
De gesynthetiseerde Equals(R)
retourneert true
als en alleen als voor elk instantieveld fieldN
in de recordstructuur de waarde van System.Collections.Generic.EqualityComparer<TN>.Default.Equals(fieldN, other.fieldN)
, waarbij TN
het veldtype is, true
is.
De recordstructuur bevat gesynthetiseerde ==
- en !=
-operators die als volgt zijn gedeclareerd:
public static bool operator==(R r1, R r2)
=> r1.Equals(r2);
public static bool operator!=(R r1, R r2)
=> !(r1 == r2);
De Equals
methode die wordt aangeroepen door de operator ==
is de hierboven opgegeven Equals(R other)
methode. De operator !=
delegeert aan de operator ==
. Dit is een fout als de operators expliciet worden gedeclareerd.
De recordstruct bevat een gesynthetiseerde override die gelijkwaardig is aan een methode die als volgt is gedeclareerd:
public override readonly bool Equals(object? obj);
Het is een fout als de overschrijving expliciet wordt gedefinieerd.
De gesynthetiseerde override retourneert other is R temp && Equals(temp)
waar R
de record struct is.
De recordstructuur omvat een gesynthetiseerde override die overeenkomt met een methode die als volgt is gedeclareerd:
public override readonly int GetHashCode();
De methode kan expliciet worden gedeclareerd.
Er wordt een waarschuwing gerapporteerd als een van Equals(R)
en GetHashCode()
expliciet wordt gedeclareerd, maar de andere methode is niet expliciet.
De gesynthetiseerde overschrijving van GetHashCode()
retourneert een int
resultaat van het combineren van de waarden van System.Collections.Generic.EqualityComparer<TN>.Default.GetHashCode(fieldN)
voor elk exemplaarveld fieldN
, waarbij TN
het type van fieldN
is.
Denk bijvoorbeeld aan de volgende recordstruct:
record struct R1(T1 P1, T2 P2);
Voor deze recordstruct zouden de gesynthetiseerde gelijkheidsleden ongeveer als volgt zijn:
struct R1 : IEquatable<R1>
{
public T1 P1 { get; set; }
public T2 P2 { get; set; }
public override bool Equals(object? obj) => obj is R1 temp && Equals(temp);
public bool Equals(R1 other)
{
return
EqualityComparer<T1>.Default.Equals(P1, other.P1) &&
EqualityComparer<T2>.Default.Equals(P2, other.P2);
}
public static bool operator==(R1 r1, R1 r2)
=> r1.Equals(r2);
public static bool operator!=(R1 r1, R1 r2)
=> !(r1 == r2);
public override int GetHashCode()
{
return Combine(
EqualityComparer<T1>.Default.GetHashCode(P1),
EqualityComparer<T2>.Default.GetHashCode(P2));
}
}
Het afdrukken van leden: methoden PrintMembers en ToString
De recordstruct bevat een gesynthetiseerde methode die overeenkomt met een methode die als volgt is gedeclareerd:
private bool PrintMembers(System.Text.StringBuilder builder);
De methode doet het volgende:
- voor elk van de afdrukbare leden van de recordstructuur (openbare niet-statische velden en leesbare eigenschappen), voegt de naam van dat lid toe gevolgd door " = " gevolgd door de waarde van het lid, gescheiden door ", "
- Waar teruggeven als de recordstruct afdrukbare leden heeft.
Voor een lid dat een waardetype heeft, converteren we de waarde ervan naar een tekenreeksweergave met behulp van de meest efficiënte methode die beschikbaar is voor het doelplatform. Op dit moment betekent dat het aanroepen van ToString
voordat u doorgeeft aan StringBuilder.Append
.
Als de afdrukbare leden van de record geen leesbare eigenschap met een niet-readonly
get
accessor bevatten, wordt de gesynthetiseerde PrintMembers
readonly
. Er is geen vereiste dat de velden van het record readonly
zijn voor de methode PrintMembers
om readonly
te kunnen zijn.
De methode PrintMembers
kan expliciet worden gedeclareerd.
Dit is een fout als de expliciete declaratie niet overeenkomt met de verwachte handtekening of toegankelijkheid.
De recordstruct bevat een gesynthetiseerde methode die overeenkomt met een methode die als volgt is gedeclareerd:
public override string ToString();
Als de PrintMembers
-methode van het recordstructuur readonly
is, dan is de gesynthetiseerde ToString()
-methode readonly
.
De methode kan expliciet worden gedeclareerd. Dit is een fout als de expliciete declaratie niet overeenkomt met de verwachte handtekening of toegankelijkheid.
De gesynthetiseerde methode:
- maakt een
StringBuilder
instantie, - voegt de recordstructnaam toe aan de builder, gevolgd door " { "
- roept de
PrintMembers
-methode van de recordstructuur aan, waarbij de opbouwfunctie wordt meegegeven, gevolgd door een spatie als deze true retourneert, - voegt "}" toe,
- retourneert de inhoud van de builder met
builder.ToString()
.
Denk bijvoorbeeld aan de volgende recordstruct:
record struct R1(T1 P1, T2 P2);
Voor deze recordstructuur zouden de gesynthetiseerde afdrukcomponenten er ongeveer als volgt uitzien:
struct R1 : IEquatable<R1>
{
public T1 P1 { get; set; }
public T2 P2 { get; set; }
private bool PrintMembers(StringBuilder builder)
{
builder.Append(nameof(P1));
builder.Append(" = ");
builder.Append(this.P1); // or builder.Append(this.P1.ToString()); if P1 has a value type
builder.Append(", ");
builder.Append(nameof(P2));
builder.Append(" = ");
builder.Append(this.P2); // or builder.Append(this.P2.ToString()); if P2 has a value type
return true;
}
public override string ToString()
{
var builder = new StringBuilder();
builder.Append(nameof(R1));
builder.Append(" { ");
if (PrintMembers(builder))
builder.Append(" ");
builder.Append("}");
return builder.ToString();
}
}
Positionele leden van recordsstructuur
Naast de bovenstaande leden, maken recordstructs met een parameterlijst ('positionele records') extra leden aan onder dezelfde voorwaarden als de leden hierboven.
Primaire constructor
Een recordstruct heeft een openbare constructor waarvan de handtekening overeenkomt met de waardeparameters van de typedeclaratie. Dit wordt de primaire constructor voor het type genoemd. Het is een fout om een primaire constructor en een constructor met dezelfde signatuur al in de struct te hebben. Als de typedeclaratie geen parameterlijst bevat, wordt er geen primaire constructor gegenereerd.
record struct R1
{
public R1() { } // ok
}
record struct R2()
{
public R2() { } // error: 'R2' already defines constructor with same parameter types
}
Declaraties van exemplaarvelden voor een recordstruct kunnen variabele initializers bevatten. Als er geen primaire constructor is, worden de initialisatieprogramma's van het exemplaar uitgevoerd als onderdeel van de parameterloze constructor. Anders voert de primaire constructor tijdens runtime de initialisatieprogramma's van het exemplaar uit die worden weergegeven in de record-struct-body.
Als een recordstruct een primaire constructor heeft, moet elke door de gebruiker gedefinieerde constructor een expliciete this
initialisatiefunctie voor constructors hebben die de primaire constructor of een expliciet gedeclareerde constructor aanroept.
Parameters van de primaire constructor en leden van de recordstruct vallen binnen het bereik van initialisaties van velden of eigenschappen van een instantie. Instantiële leden zouden een fout zijn op deze locaties (vergelijkbaar met hoe instantiële leden momenteel binnen het bereik vallen in reguliere constructor-initialisaties, maar het gebruik ervan een fout oplevert), maar de parameters van de primaire constructor zouden binnen het bereik en bruikbaar zijn en leden overschaduwen. Statische leden kunnen ook worden gebruikt.
Er wordt een waarschuwing gegenereerd als een parameter van de primaire constructor niet wordt gelezen.
De definitieve toewijzingsregels voor constructors van struct-instanties zijn van toepassing op de primaire constructor van recordstructuren. Het volgende is bijvoorbeeld een fout:
record struct Pos(int X) // definite assignment error in primary constructor
{
private int x;
public int X { get { return x; } set { x = value; } } = X;
}
Eigenschappen
Voor elke record-structuurparameter van een record-structuurdeclaratie is er een corresponderende openbare eigenschap waarvan de naam en het type afkomstig zijn van de declaratie van de waardeparameter.
Voor een recordstructuur:
- Er wordt een openbare
get
eninit
automatische eigenschap gemaakt als de recordstructuur dereadonly
-modificator heeft; andersget
enset
. Beide soorten set-accessors (set
eninit
) worden als 'overeenkomend' beschouwd. De gebruiker kan dus een init-only eigenschap declareren in plaats van een gesynthetiseerde veranderlijke eigenschap. Een overgenomenabstract
eigenschap met overeenkomend type wordt overschreven. Er wordt geen automatische eigenschap gemaakt als de recordstructuur een exemplaarveld heeft met de verwachte naam en het verwachte type. Dit is een fout als de overgenomen eigenschap geenpublic
get
enset
/init
accessors heeft. Dit is een fout als de overgenomen eigenschap of het overgenomen veld verborgen is.
De automatische eigenschap wordt geïnitialiseerd naar de waarde van de bijbehorende primaire constructorparameter. Kenmerken kunnen worden toegepast op de gesynthetiseerde automatische eigenschap en het bijbehorende backingveld met behulp vanproperty:
- offield:
-targets voor kenmerken die syntactisch zijn toegepast op de bijbehorende record struct parameter.
Deconstruct
Een positionele recordstruct met ten minste één parameter synthetiseert een openbare void-achterlatende instantiemethode met de naam Deconstruct
met een out parameterdeclaratie voor elke parameter van de primaire constructordeclaratie. Elke parameter van de deconstruct-methode heeft hetzelfde type als de bijbehorende parameter van de primaire constructordeclaratie. De hoofdtekst van de methode wijst elke parameter van de Deconstruct-methode toe aan de waarde van een exemplaarlidtoegang tot een lid van dezelfde naam.
Als de instantieleden die in de hoofdtekst worden benaderd, geen eigenschap bevatten met een niet-readonly
get
accessor, dan wordt de gesynthetiseerde Deconstruct
-methode readonly
.
De methode kan expliciet worden gedeclareerd. Dit is een fout als de expliciete declaratie niet overeenkomt met de verwachte handtekening of toegankelijkheid, of statisch is.
with
-expressie voor structs toestaan
Het is nu geldig voor de ontvanger in een with
-expressie om een structtype te hebben.
Aan de rechterkant van de with
expressie bevindt zich een member_initializer_list
met een reeks toewijzingen aan id, die een toegankelijk exemplaarveld of een toegankelijke eigenschap van het type ontvanger moeten zijn.
Voor een ontvanger met structtype wordt de ontvanger eerst gekopieerd en wordt elke member_initializer
op dezelfde manier verwerkt als een toewijzing aan een veld- of eigenschapstoegang van het resultaat van de conversie.
Opdrachten worden verwerkt in lexicale volgorde.
Verbeteringen aan records
record class
toestaan
De bestaande syntaxis voor recordtypen staat record class
toe met dezelfde betekenis als record
.
record_declaration
: attributes? class_modifier* 'partial'? 'record' 'class'? identifier type_parameter_list?
parameter_list? record_base? type_parameter_constraints_clause* record_body
;
Door de gebruiker gedefinieerde positionele leden mogen velden zijn
Er wordt geen automatische eigenschap gemaakt als de record een exemplaarveld met de verwachte naam en het verwachte type heeft of overneemt.
Parameterloze constructors en lid-initializers in structs toestaan
Zie parameterloze struct constructors specificatie.
Open vragen
- Hoe kun je recordstructuren in metagegevens herkennen? (we hebben geen onuitsprekelijke kloonmethode om te benutten...)
Beantwoordt
- bevestig dat we het ontwerp van PrintMembers willen behouden (afzonderlijke methode die
bool
retourneert) (antwoord: ja) - bevestig dat we
record ref struct
niet toestaan (probleem metIEquatable<RefStruct>
- en verwijzingsvelden) (antwoord: ja) - bevestig de implementatie van leden voor gelijkheid. Alternatief is dat gesynthetiseerde
bool Equals(R other)
,bool Equals(object? other)
en operators allemaal gewoon delegeren aanValueType.Equals
. (antwoord: ja) - bevestig dat we veld initialisaties willen toestaan wanneer er een primaire constructor is. Willen we ook parameterloze struct constructors toestaan terwijl we eraan zitten (het Activator-probleem is blijkbaar opgelost)? (antwoord: ja, bijgewerkte specificatie moet worden beoordeeld in LDM)
- Hoeveel willen we zeggen over
Combine
methode? (antwoord: zo weinig mogelijk) - Moeten we een door de gebruiker gedefinieerde constructor met de signatuur van een kopieconstructor uitsluiten? (antwoord: nee, er is geen concept van een kopieerconstructor in de record structs specificatie)
- bevestig dat we leden met de naam 'Clone' willen weigeren. (antwoord: juist)
- controleer of gesynthetiseerde
Equals
logica functioneel gelijk is aan runtime-implementatie (bijvoorbeeld float. NaN) (antwoord: bevestigd in LDM) - kunnen veld- of eigenschapsdoelkenmerken worden geplaatst in de lijst met positionele parameters? (antwoord: ja, hetzelfde als voor de recordklasse)
-
with
op generics? (antwoord: buiten het bereik voor C# 10) - moet
GetHashCode
een hash van het type zelf bevatten om verschillende waarden op te halen tussenrecord struct S1;
enrecord struct S2;
? (antwoord: nee)
C# feature specifications