Delen via


Archief

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 notities van de taalontwerpvergadering ().

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

In dit voorstel wordt de specificatie voor de recordfunctie C# 9 bijgehouden, zoals overeengekomen door het C#-taalontwerpteam.

De syntaxis voor een record is als volgt:

record_declaration
    : attributes? class_modifier* 'partial'? 'record' identifier type_parameter_list?
      parameter_list? record_base? type_parameter_constraints_clause* record_body
    ;

record_base
    : ':' class_type argument_list?
    | ':' interface_type_list
    | ':' class_type argument_list? ',' interface_type_list
    ;

record_body
    : '{' class_member_declaration* '}' ';'?
    | ';'
    ;

Recordtypen zijn referentietypen, vergelijkbaar met een klassedeclaratie. Het is een fout voor een record om een record_baseargument_list op te geven als de record_declaration geen parameter_listbevat. Ten hoogste één gedeeltelijke declaratie van een gedeeltelijke gegevensrecord kan een parameter_listbevatten.

Recordparameters kunnen geen ref, out of this modifiers gebruiken (maar in en params zijn toegestaan).

Erfenis

Records kunnen niet erven van klassen, tenzij het de klasse objectbetreft, en klassen kunnen niet erven van records. Records kunnen erven van andere records.

Leden van een recordtype

Naast de leden die zijn gedeclareerd in de hoofdtekst van de record, heeft een recordtype extra gesynthetiseerde leden. Leden worden gesynthetiseerd tenzij een lid met een 'overeenkomende' handtekening wordt gedeclareerd in de hoofdtekst van de record of een toegankelijk concreet niet-virtueel lid met een 'overeenkomende' handtekening wordt overgenomen. Een overeenkomend lid voorkomt dat de compiler dat lid genereert, geen andere gesynthetiseerde leden. Twee leden worden beschouwd als overeenkomend als ze dezelfde handtekening hebben of als 'verbergen' worden beschouwd in een overnamescenario. Het is een fout dat een lid van een record de naam 'Clone' krijgt. Het is een fout als een exemplaarveld van een record een pointertype op topniveau heeft. Een geneste aanwijzer, zoals een matrix met aanwijzers, is toegestaan.

De gesynthetiseerde leden zijn als volgt:

Leden voor Gelijkheid

Als de record is afgeleid van object, bevat het recordtype een gesynthetiseerde alleen-lezeneigenschap die gelijk is aan een eigenschap die als volgt is gedeclareerd:

Type EqualityContract { get; }

De eigenschap is private als het recordtype sealedis. Anders is de eigenschap virtual en protected. De eigenschap kan expliciet worden gedeclareerd. Dit is een fout als de expliciete declaratie niet overeenkomt met de verwachte handtekening of toegankelijkheid, of als de expliciete declaratie niet toestaat dat deze wordt overschreven in een afgeleid type en het recordtype niet sealedis.

Als het recordtype is afgeleid van een basisrecordtype Base, bevat het recordtype een gesynthetiseerde leeseigenschap die overeenkomt met een eigenschap die als volgt is gedeclareerd:

protected override Type EqualityContract { get; }

De eigenschap kan expliciet worden gedeclareerd. Dit is een fout als de expliciete declaratie niet overeenkomt met de verwachte handtekening of toegankelijkheid, of als de expliciete declaratie niet toestaat dat deze wordt overschreven in een afgeleid type en het recordtype niet sealedis. Dit is een fout als een gesynthetiseerde of expliciet gedeclareerde eigenschap een eigenschap met deze handtekening niet overschrijft in het recordtype Base (bijvoorbeeld als de eigenschap ontbreekt in het Base, of verzegeld, of niet virtueel, enzovoort). De gesynthetiseerde eigenschap retourneert typeof(R) waar R het recordtype is.

Het recordtype implementeert System.IEquatable<R> en bevat een sterk gesynthetiseerde overbelasting van Equals(R? other) waarbij R het recordtype is. De methode is publicen de methode is virtual tenzij het recordtype sealedis. De methode kan expliciet worden gedeclareerd. Dit is een fout als de expliciete verklaring niet overeenkomt met de verwachte signature of toegankelijkheid, of als de expliciete verklaring geen overschrijding toestaat in een afgeleid type, en het recordtype niet sealedis.

Als Equals(R? other) door de gebruiker gedefinieerd (niet gesynthetiseerd) maar GetHashCode niet is, wordt er een waarschuwing gegenereerd.

public virtual bool Equals(R? other);

De gesynthetiseerde Equals(R?) retourneert true alleen als en slechts als elk van de volgende true:

  • other is niet nullen
  • Voor elk exemplaarveld fieldN in het recordtype dat niet is overgenomen, de waarde van System.Collections.Generic.EqualityComparer<TN>.Default.Equals(fieldN, other.fieldN) waarbij TN het veldtype is en
  • Als er een basisrecordtype is, is de waarde van base.Equals(other) (een niet-virtuele aanroep naar public virtual bool Equals(Base? other)); anders de waarde van EqualityContract == other.EqualityContract.

Het recordtype bevat de gesynthetiseerde operatoren == en !=, die als volgt zijn gedeclareerd:

public static bool operator==(R? left, R? right)
    => (object)left == right || (left?.Equals(right) ?? false);
public static bool operator!=(R? left, R? right)
    => !(left == right);

De Equals methode die wordt aangeroepen door de operator == is de hierboven opgegeven Equals(R? other) methode. De operator != delegeert naar de operator ==. Dit is een fout als de operators expliciet worden gedeclareerd.

Als het recordtype is afgeleid van een basisrecordtype Base, bevat het recordtype een gesynthetiseerde override die vergelijkbaar is met een als volgt gedeclareerde methode:

public sealed override bool Equals(Base? other);

Het is een fout als de overschrijving expliciet wordt gedeclareerd. Dit is een fout als de methode een methode met dezelfde handtekening in recordtype Base niet overschrijft (bijvoorbeeld als de methode ontbreekt in de Base, of verzegeld of niet virtueel, enzovoort). De gesynthetiseerde overschrijding retourneert Equals((object?)other).

Het recordtype bevat een gesynthetiseerde override die gelijkstaat aan een methode die als volgt is gedeclareerd:

public override bool Equals(object? obj);

Het is een fout als de overschrijving expliciet wordt gedeclareerd. Dit is een fout als de methode object.Equals(object? obj) niet overschrijft (bijvoorbeeld vanwege schaduw in tussenliggende basistypen, enzovoort). De gesynthetiseerde override geeft Equals(other as R) terug waar R het recordtype is.

Het recordtype bevat een gesynthetiseerde override die gelijkstaat aan een methode die als volgt is gedeclareerd:

public override int GetHashCode();

De methode kan expliciet worden gedeclareerd. Het is een fout als de expliciete declaratie het niet toestaat om deze te overschrijven in een afgeleid type en het recordtype niet sealedis. Dit is een fout als de gesynthetiseerde of expliciet gedeclareerde methode object.GetHashCode() niet overschrijft (bijvoorbeeld vanwege schaduw in tussenliggende basistypen, enzovoort).

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() geeft een int resultaat door het combineren van de volgende waarden:

  • Voor elk exemplaarveld fieldN in het recordtype dat niet is overgenomen, de waarde van System.Collections.Generic.EqualityComparer<TN>.Default.GetHashCode(fieldN) waarbij TN het veldtype is en
  • Als er een basisrecordtype is, dan is de waarde base.GetHashCode(); anders is de waarde System.Collections.Generic.EqualityComparer<System.Type>.Default.GetHashCode(EqualityContract).

Denk bijvoorbeeld aan de volgende recordtypen:

record R1(T1 P1);
record R2(T1 P1, T2 P2) : R1(P1);
record R3(T1 P1, T2 P2, T3 P3) : R2(P1, P2);

Voor deze recordtypen zouden de gesynthetiseerde gelijkheidskenmerken ongeveer als volgt zijn:

class R1 : IEquatable<R1>
{
    public T1 P1 { get; init; }
    protected virtual Type EqualityContract => typeof(R1);
    public override bool Equals(object? obj) => Equals(obj as R1);
    public virtual bool Equals(R1? other)
    {
        return !(other is null) &&
            EqualityContract == other.EqualityContract &&
            EqualityComparer<T1>.Default.Equals(P1, other.P1);
    }
    public static bool operator==(R1? left, R1? right)
        => (object)left == right || (left?.Equals(right) ?? false);
    public static bool operator!=(R1? left, R1? right)
        => !(left == right);
    public override int GetHashCode()
    {
        return HashCode.Combine(EqualityComparer<Type>.Default.GetHashCode(EqualityContract),
            EqualityComparer<T1>.Default.GetHashCode(P1));
    }
}

class R2 : R1, IEquatable<R2>
{
    public T2 P2 { get; init; }
    protected override Type EqualityContract => typeof(R2);
    public override bool Equals(object? obj) => Equals(obj as R2);
    public sealed override bool Equals(R1? other) => Equals((object?)other);
    public virtual bool Equals(R2? other)
    {
        return base.Equals((R1?)other) &&
            EqualityComparer<T2>.Default.Equals(P2, other.P2);
    }
    public static bool operator==(R2? left, R2? right)
        => (object)left == right || (left?.Equals(right) ?? false);
    public static bool operator!=(R2? left, R2? right)
        => !(left == right);
    public override int GetHashCode()
    {
        return HashCode.Combine(base.GetHashCode(),
            EqualityComparer<T2>.Default.GetHashCode(P2));
    }
}

class R3 : R2, IEquatable<R3>
{
    public T3 P3 { get; init; }
    protected override Type EqualityContract => typeof(R3);
    public override bool Equals(object? obj) => Equals(obj as R3);
    public sealed override bool Equals(R2? other) => Equals((object?)other);
    public virtual bool Equals(R3? other)
    {
        return base.Equals((R2?)other) &&
            EqualityComparer<T3>.Default.Equals(P3, other.P3);
    }
    public static bool operator==(R3? left, R3? right)
        => (object)left == right || (left?.Equals(right) ?? false);
    public static bool operator!=(R3? left, R3? right)
        => !(left == right);
    public override int GetHashCode()
    {
        return HashCode.Combine(base.GetHashCode(),
            EqualityComparer<T3>.Default.GetHashCode(P3));
    }
}

Leden kopiëren en klonen

Een recordtype bevat twee kopieerleden:

  • Een constructor die één argument van het recordtype gebruikt. Dit wordt een 'copy constructor' genoemd.
  • Een gesynthetiseerde openbare parameterloze instantie 'clone'-methode met een door compiler gereserveerde naam

Het doel van de copyconstructor is om de staat van de parameter naar het nieuwe exemplaar dat wordt gemaakt te kopiëren. Met deze constructor worden geen initialisatiefuncties voor instantievelden/eigenschappen uitgevoerd die aanwezig zijn in de recorddeclaratie. Als de constructor niet expliciet wordt gedeclareerd, wordt een constructor door de compiler gesynthetiseerd. Als de record is verzegeld, is de constructor privé, anders wordt deze beveiligd. Een expliciet gedeclareerde kopieerconstructor moet openbaar of beveiligd zijn, tenzij de record is verzegeld. Het eerste wat de constructor moet doen, is een kopieerconstructor van de basis aanroepen of een objectconstructor zonder parameters als het record erft van object. Er wordt een fout gerapporteerd als een door de gebruiker gedefinieerde kopieerconstructor gebruikmaakt van een impliciete of expliciete constructor-initialisatiefunctie die niet aan deze vereiste voldoet. Nadat een basiskopieconstructor is aangeroepen, kopieert een gesynthetiseerde kopieerconstructor waarden voor alle exemplaarvelden impliciet of expliciet gedeclareerd binnen het recordtype. De enige aanwezigheid van een kopieerconstructor, expliciet of impliciet, voorkomt geen automatische toevoeging van een standaardexemplaarconstructor.

Als een virtuele kloonmethode aanwezig is in de basisrecord, overschrijft de gesynthetiseerde kloonmethode deze en is het retourtype van de methode het huidige type dat het bevat. Er wordt een fout gegenereerd als de kloonmethode van de basisrecord is afgesloten. Als een virtuele kloonmethode niet aanwezig is in de basisrecord, is het retourtype van de kloonmethode het type dat de kloon bevat en de methode virtueel is, tenzij de record is verzegeld of abstract. Als de records abstract zijn, is de gesynthetiseerde kloonmethode ook abstract. Als de kloonmethode niet abstract is, wordt het resultaat van een aanroep naar een kopieerconstructor geretourneerd.

Afdrukken van leden: Methoden PrintMembers en ToString

Als de record is afgeleid van object, bevat de record een gesynthetiseerde methode die gelijk is aan een methode die als volgt wordt gedeclareerd:

bool PrintMembers(System.Text.StringBuilder builder);

De methode is private als het recordtype sealedis. Anders wordt de methode virtual en protected.

De methode:

  1. roept de methode System.Runtime.CompilerServices.RuntimeHelpers.EnsureSufficientExecutionStack() aan als de methode aanwezig is en het record afdrukbare leden heeft.
  2. voor elk van de afdrukbare leden van het record (niet-statisch openbaar veld en leesbare eigenschapsleden), wordt de naam van dat lid toegevoegd gevolgd door " = " en vervolgens de waarde van het lid, gescheiden door ", ".
  3. Geef true terug als het record 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 het recordtype is afgeleid van een basisrecord Base, bevat het record een gesynthetiseerde overschrijving die gelijk is aan een methode die als volgt wordt gedeclareerd:

protected override bool PrintMembers(StringBuilder builder);

Als de record geen afdrukbare leden heeft, roept de methode de basismethode PrintMembers aan met één argument (de parameter builder) en retourneert het resultaat.

Anders, de methode:

  1. roept de basismethode PrintMembers aan met één argument (de parameter builder),
  2. als de PrintMembers-methode true heeft geretourneerd, voegt u ', ' toe aan de opbouwfunctie,
  3. voor elk van de afdrukbare leden van de record voegt u de naam van dat lid toe gevolgd door " = " gevolgd door de waarde van het lid: this.member (of this.member.ToString() voor waardetypen), gescheiden door ", ",
  4. geef waar terug.

De methode PrintMembers kan expliciet worden gedeclareerd. Dit is een fout als de expliciete declaratie niet overeenkomt met de verwachte handtekening of toegankelijkheid, of als de expliciete declaratie niet toestaat dat deze wordt overschreven in een afgeleid type en het recordtype niet sealedis.

De record bevat een gesynthetiseerde methode die gelijk is aan een methode die als volgt is gedeclareerd:

public override string ToString();

De methode kan expliciet worden gedeclareerd. Dit is een fout als de expliciete declaratie niet overeenkomt met de verwachte handtekening of toegankelijkheid, of als de expliciete declaratie niet toestaat dat deze wordt overschreven in een afgeleid type en het recordtype niet sealedis. Dit is een fout als de gesynthetiseerde of expliciet gedeclareerde methode object.ToString() niet overschrijft (bijvoorbeeld vanwege schaduw in tussenliggende basistypen, enzovoort).

De gesynthetiseerde methode:

  1. maakt een StringBuilder instantie,
  2. voegt de recordnaam toe aan de builder, gevolgd door " { ",
  3. roept de PrintMembers methode van de record aan, die de opbouwfunctie geeft, gevolgd door ' ' als deze waar retourneert,
  4. voegt "}" toe,
  5. retourneert de inhoud van de builder met behulp van builder.ToString().

Denk bijvoorbeeld aan de volgende recordtypen:

record R1(T1 P1);
record R2(T1 P1, T2 P2, T3 P3) : R1(P1);

Voor deze recordtypen zijn de gesynthetiseerde afdrukleden ongeveer als volgt:

class R1 : IEquatable<R1>
{
    public T1 P1 { get; init; }
    
    protected virtual bool PrintMembers(StringBuilder builder)
    {
        builder.Append(nameof(P1));
        builder.Append(" = ");
        builder.Append(this.P1); // or builder.Append(this.P1.ToString()); if T1 is 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();
    }
}

class R2 : R1, IEquatable<R2>
{
    public T2 P2 { get; init; }
    public T3 P3 { get; init; }
    
    protected override bool PrintMembers(StringBuilder builder)
    {
        if (base.PrintMembers(builder))
            builder.Append(", ");
            
        builder.Append(nameof(P2));
        builder.Append(" = ");
        builder.Append(this.P2); // or builder.Append(this.P2); if T2 is a value type
        
        builder.Append(", ");
        
        builder.Append(nameof(P3));
        builder.Append(" = ");
        builder.Append(this.P3); // or builder.Append(this.P3); if T3 is a value type
        
        return true;
    }
    
    public override string ToString()
    {
        var builder = new StringBuilder();
        builder.Append(nameof(R2));
        builder.Append(" { ");

        if (PrintMembers(builder))
            builder.Append(" ");

        builder.Append("}");
        return builder.ToString();
    }
}

Leden van positionele records

Naast de bovenstaande leden, synthetiseren records met een parameterlijst ('positionele records') extra leden onder dezelfde voorwaarden als de bovenstaande leden.

Primaire constructor

Een recordtype heeft een openbare constructor waarvan de handtekening overeenkomt met de waardeparameters van de typedeclaratie. Dit wordt de primaire constructor voor het type genoemd en zorgt ervoor dat de impliciet gedeclareerde standaardklasseconstructor, indien aanwezig, wordt onderdrukt. Het is een fout om een primaire constructor en een constructor met dezelfde signatuur al aanwezig te hebben in de class.

Tijdens runtime de primaire constructor

  1. voert de initialisatoren van het exemplaar uit die in de klasse-inhoud worden weergegeven

  2. roept de basisklasseconstructor aan met de argumenten die zijn opgegeven in de record_base-component, indien aanwezig

Als een record een primaire constructor heeft, moet elke door de gebruiker gedefinieerde constructor, behalve de 'kopieconstructor', een expliciete this constructor-initializer hebben.

Parameters van de primaire constructor evenals leden van de record zijn beschikbaar binnen de argument_list van de record_base clausule en binnen initialisaties van instantievelden of eigenschappen. Exemplaarleden zouden een fout zijn op deze locaties (vergelijkbaar met hoe exemplaarleden binnen het bereik zijn in reguliere constructorinitialisaties vandaag de dag, maar waarbij het gebruik ervan tot een fout leidt), terwijl de parameters van de primaire constructor binnen het bereik zouden zijn, gebruikt kunnen worden en voorrang zouden hebben op leden. Statische leden zouden ook bruikbaar zijn, vergelijkbaar met hoe basisoproepen en initializers werken in gewone constructors.

Er wordt een waarschuwing gegenereerd als een parameter van de primaire constructor niet wordt gelezen.

Expressievariabelen die zijn gedeclareerd in de argument_list vallen binnen het bereik van de argument_list. Dezelfde schaduwregels als binnen een argumentenlijst van een reguliere constructor-initialisatiefunctie zijn van toepassing.

Eigenschappen

Voor elke recordparameter van een recordtypedeclaratie is er een corresponderend openbaar eigenschapslid waarvan de naam en het type zijn afgeleid van de declaratie van de waardeparameter.

Voor een record:

  • Er wordt een openbare get en init autoeigenschap gemaakt (zie afzonderlijke init accessorspecificatie). Een overgenomen abstract eigenschap met overeenkomend type wordt overschreven. Dit is een fout als de overgenomen eigenschap geen public kan worden overschreven get en init accessors. Dit is een fout als de overgenomen eigenschap verborgen is.
    De automatische eigenschap wordt geïnitialiseerd op basis van de waarde van de bijbehorende primaire constructorparameter. Kenmerken kunnen worden toegepast op de gesynthetiseerde automatische eigenschap en het bijbehorende achtergrondveld met behulp van property: of field: doel voor kenmerken die syntactisch worden toegepast op de corresponderende recordparameter.

Deconstrueren

Een positionele record met ten minste één parameter synthetiseert een openbare instantiemethode genaamd Deconstruct met een declaratie van een out-parameter voor elke parameter van de primaire constructor. Elke parameter van de methode Deconstruct heeft hetzelfde type als de bijbehorende parameter van de primaire constructordeclaratie. De hoofdtekst van de methode wijst aan elke parameter van de methode Deconstruct de waarde toe van de instantie-eigenschap met dezelfde naam. 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.

In het volgende voorbeeld ziet u een positionele record R met de compilatiemethode Deconstruct, samen met het gebruik ervan:

public record R(int P1, string P2 = "xyz")
{
    public void Deconstruct(out int P1, out string P2)
    {
        P1 = this.P1;
        P2 = this.P2;
    }
}

class Program
{
    static void Main()
    {
        R r = new R(12);
        (int p1, string p2) = r;
        Console.WriteLine($"p1: {p1}, p2: {p2}");
    }
}

with-expressie

Een with-expressie is een nieuwe expressie met behulp van de volgende syntaxis.

with_expression
    : switch_expression
    | switch_expression 'with' '{' member_initializer_list? '}'
    ;

member_initializer_list
    : member_initializer (',' member_initializer)*
    ;

member_initializer
    : identifier '=' expression
    ;

Een with-expressie mag niet worden gebruikt als uitdrukking.

Een with expressie maakt "niet-destructieve mutatie" mogelijk, ontworpen om een kopie van de ontvangerexpressie te produceren met wijzigingen in toewijzingen in de member_initializer_list.

Een geldige with-expressie heeft een ontvanger met een niet-lege type. Het type ontvanger moet een record zijn.

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.

Eerst wordt de kloonmethode van de ontvanger (hierboven opgegeven) aangeroepen en wordt het resultaat geconverteerd naar het type van de ontvanger. Vervolgens 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.