Delen via


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_listbieden. 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, trueis.

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 fieldNis.

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:

  1. 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 ", "
  2. 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-readonlyget accessor bevatten, wordt de gesynthetiseerde PrintMembersreadonly. Er is geen vereiste dat de velden van het record readonly zijn voor de methode PrintMembers om readonlyte 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 readonlyis, 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:

  1. maakt een StringBuilder instantie,
  2. voegt de recordstructnaam toe aan de builder, gevolgd door " { "
  3. roept de PrintMembers-methode van de recordstructuur aan, waarbij de opbouwfunctie wordt meegegeven, gevolgd door een spatie als deze true retourneert,
  4. voegt "}" toe,
  5. 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 en init automatische eigenschap gemaakt als de recordstructuur de readonly-modificator heeft; anders get en set. Beide soorten set-accessors (set en init) worden als 'overeenkomend' beschouwd. De gebruiker kan dus een init-only eigenschap declareren in plaats van een gesynthetiseerde veranderlijke eigenschap. Een overgenomen abstract 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 geen publicget en set/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 van property:- of field:-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-readonlyget 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

Zie https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-10-05.md#changing-the-member-type-of-a-primary-constructor-parameter

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 boolretourneert) (antwoord: ja)
  • bevestig dat we record ref struct niet toestaan (probleem met IEquatable<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 aan ValueType.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 tussen record struct S1; en record struct S2;? (antwoord: nee)