Dela via


Arkiv

Notera

Den här artikeln är en funktionsspecifikation. Specifikationen fungerar som designdokument för funktionen. Den innehåller föreslagna specifikationsändringar, tillsammans med information som behövs under utformningen och utvecklingen av funktionen. Dessa artiklar publiceras tills de föreslagna specifikationsändringarna har slutförts och införlivats i den aktuella ECMA-specifikationen.

Det kan finnas vissa skillnader mellan funktionsspecifikationen och den slutförda implementeringen. Dessa skillnader samlas in i de relevanta LDM-anteckningar (Language Design Meeting).

Du kan läsa mer om processen för att införa funktionsspecifikationer i C#-språkstandarden i artikeln om specifikationerna.

Det här förslaget spårar specifikationen för C# 9-postfunktionen, enligt överenskommelse med C#-språkdesignteamet.

Syntaxen för en post är följande:

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* '}' ';'?
    | ';'
    ;

Datatyper är referenstyper som motsvarar en deklaration av en klass. Det är ett fel för en post att ange en record_baseargument_list om record_declaration inte innehåller en parameter_list. Högst en partiell typdeklaration av en partiell record kan innehålla en parameter_list.

Postparametrar kan inte använda ref, out eller this modifier (men in och params tillåts).

Arv

Poster kan inte ärva från klasser, om inte klassen är object, och klasser kan inte ärva från poster. Poster kan ärva från andra poster.

Medlemmar av en posttyp

Förutom de medlemmar som deklareras i posttexten har en posttyp ytterligare syntetiserade medlemmar. Medlemmar syntetiseras om inte en medlem med en "matchande" signatur deklareras i posttexten eller en tillgänglig konkret icke-virtuell medlem med en "matchande" signatur ärvs. En matchande medlem hindrar kompilatorn från att generera medlemmen, inte andra syntetiserade medlemmar. Två medlemmar anses matcha om de har samma signatur eller skulle betraktas som "gömmer sig" i ett arvsscenario. Det är ett fel att en medlem i en post får namnet "Clone". Det är ett fel när ett instansfält i en record har en osäker typ.

De syntetiserade medlemmarna är följande:

Jämställdhetsmedlemmar

Om posten härleds från objectinnehåller posttypen en syntetiserad skrivskyddad egenskap som motsvarar en egenskap som deklareras enligt följande:

Type EqualityContract { get; }

Egenskapen är private om posttypen är sealed. Annars är egenskapen virtual samt protected. Egenskapen kan deklareras explicit. Det är ett fel om den explicita deklarationen inte matchar den förväntade signaturen eller tillgängligheten eller om den explicita deklarationen inte tillåter att den åsidosätts i en härledd typ och posttypen inte är sealed.

Om posttypen härleds från en basposttyp Base, innehåller posttypen en syntetiserad skrivskyddad egenskap motsvarande en egenskap deklarerad på följande sätt:

protected override Type EqualityContract { get; }

Egenskapen kan deklareras tydligt. Det är ett fel om den explicita deklarationen inte matchar den förväntade signaturen eller tillgängligheten, eller om den explicita deklarationen inte tillåter åsidosättande av den i en härledd typ och posttypen inte är sealed. Det är ett fel om varken en syntetiserad eller en explicit deklarerad egenskap åsidosätter en egenskap med denna signatur i posttypen Base (till exempel om egenskapen saknas i Base, är förseglad, ej virtuell, och så vidare). Den syntetiserade egenskapen returnerar typeof(R) där R är posttypen.

Posttypen implementerar System.IEquatable<R> och innehåller en syntetiserad överlagring av Equals(R? other) där R är posttypen. Metoden är public, och metoden är virtual om posttypen inte är sealed. Metoden kan deklareras explicit. Det är ett fel om den explicita deklarationen inte matchar den förväntade signaturen eller tillgängligheten, eller om den explicita deklarationen inte tillåter åsidosättande av den i en härledd typ och posttypen inte är sealed.

Om Equals(R? other) är användardefinierad (inte syntetiserad) men GetHashCode inte är det genereras en varning.

public virtual bool Equals(R? other);

Den syntetiserade Equals(R?) returnerar true om och endast om var och en av följande är true:

  • other är inte null, och
  • För varje instansfält fieldN i posttypen som inte ärvs, värdet för System.Collections.Generic.EqualityComparer<TN>.Default.Equals(fieldN, other.fieldN) där TN är fälttypen och
  • Om det finns en baspost, värdet av base.Equals(other) (ett icke-virtuellt anrop till public virtual bool Equals(Base? other)); annars värdet av EqualityContract == other.EqualityContract.

Posttypen inkluderar de syntetiserade operatorerna == och != som motsvarar de operatorer som deklarerats på följande sätt:

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

Den Equals metod som anropas av operatorn == är den Equals(R? other) metod som anges ovan. Operatorn != delegerar till ==-operatorn. Det är ett fel om operatorerna deklareras explicit.

Om posttypen härleds från en basposttyp Baseinnehåller posttypen en syntetiserad åsidosättning som motsvarar en metod som deklareras enligt följande:

public sealed override bool Equals(Base? other);

Det är ett fel om åsidosättningen deklareras explicit. Det är ett fel om metoden inte åsidosätter en metod med samma signatur i posttyp Base (till exempel om metoden saknas i Base, eller förseglad eller inte virtuell osv.). Den syntetiserade åsidosättningen returnerar Equals((object?)other).

Posttypen innehåller en syntetiserad åsidosättning som motsvarar en metod som deklareras enligt följande:

public override bool Equals(object? obj);

Det är ett fel om åsidosättningen deklareras explicit. Det är ett fel om metoden inte åsidosätter object.Equals(object? obj) (till exempel på grund av skuggning i mellanliggande bastyper osv.). Den syntetiserade överskrivningen returnerar Equals(other as R) där R är posttypen.

Posttypen innehåller en syntetiserad åsidosättning som motsvarar en metod som deklareras enligt följande:

public override int GetHashCode();

Metoden kan deklareras explicit. Det är ett fel om den explicita deklarationen inte tillåter åsidosättning i en härledd typ och om rekordtypen inte är sealed. Det är ett fel om antingen syntetiserad eller explicit deklarerad metod inte åsidosätter object.GetHashCode() (till exempel på grund av skuggning i mellanliggande bastyper osv.).

En varning rapporteras om en av Equals(R?) eller GetHashCode() uttryckligen deklareras men den andra metoden inte är uttrycklig.

Den syntetiserade åsidosättningen av GetHashCode() returnerar ett int resultat av att kombinera följande värden:

  • För varje instansfält fieldN i posttypen som inte ärvs, värdet för System.Collections.Generic.EqualityComparer<TN>.Default.GetHashCode(fieldN) där TN är fälttypen och
  • Om det finns en basposttyp, är värdet base.GetHashCode(); annars är värdet System.Collections.Generic.EqualityComparer<System.Type>.Default.GetHashCode(EqualityContract).

Tänk till exempel på följande posttyper:

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

För dessa rekordtyper skulle de genererade likhetsmedlemmarna vara ungefär så här:

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

Kopiera och klona medlemmar

En posttyp innehåller två kopierande medlemmar:

  • En konstruktor som tar ett enda argument av recordtypen. Det kallas för en "kopieringskonstruktor".
  • En syntetiserad "klon"-metod för offentlig parameterlös instans med ett kompilatorreserverat namn

Syftet med kopieringskonstruktorn är att kopiera tillståndet från parametern till den nya instans som skapas. Den här konstruktorn kör inga instansfält/egenskapsinitiatorer som finns i rekordsdeklarationen. Om konstruktorn inte uttryckligen deklareras syntetiseras en konstruktor av kompilatorn. Om posten är förseglad är konstruktorn privat, annars är den skyddad. En uttryckligen deklarerad kopieringskonstruktor måste vara antingen offentlig eller skyddad, såvida inte posten är förseglad. Det första konstruktorn måste göra är att anropa en kopieringskonstruktor för basen, eller en objektkonstruktor utan parametrar om record ärver från objekt. Ett fel rapporteras om en användardefinierad kopieringskonstruktor använder en implicit eller explicit konstruktorinitierare som inte uppfyller detta krav. När en baskopieringskonstruktor anropas kopierar en syntetiserad kopieringskonstruktor värden för alla instansfält implicit eller uttryckligen deklarerade inom rekordtypen. Den enda förekomsten av en kopieringskonstruktor, oavsett om den är explicit eller implicit, förhindrar inte automatisk tilläggning av en standardinstanskonstruktor.

Om en virtuell "klon"-metod finns i basposten åsidosätter den syntetiserade "klon"-metoden den och metodens returtyp är den aktuella innehållande typen. Ett fel uppstår om baspostkloningsmetoden är förseglad. Om en virtuell "klon"-metod inte finns i basposten är returtypen för klonmetoden den innehållande typen och metoden är virtuell, såvida inte posten är förseglad eller abstrakt. Om den innehållande posten är abstrakt är den syntetiserade klonmetoden också abstrakt. Om metoden "clone" inte är abstrakt returneras resultatet av ett anrop till en kopieringskonstruktor.

Utskriftsmedlemmar: PrintMembers- och ToString-metoder

Om posten härleds från objectinnehåller posten en syntetiserad metod som motsvarar en metod som deklareras enligt följande:

bool PrintMembers(System.Text.StringBuilder builder);

Metoden är private om posttypen är sealed. Annars är metoden virtual och protected.

Metoden:

  1. anropar metoden System.Runtime.CompilerServices.RuntimeHelpers.EnsureSufficientExecutionStack() om metoden finns och posten har element som kan skrivas ut.
  2. för var och en av postens utskrivbara medlemmar (icke-statiskt offentligt fält och läsbara egenskapsmedlemmar), lägger till medlemmens namn följt av " = " följt av medlemmens värde avgränsat med ", ",
  3. returnera sant om posten har utskrivbara medlemmar.

För en medlem som har en värdetyp konverterar vi dess värde till en strängrepresentation med hjälp av den mest effektiva metoden som är tillgänglig för målplattformen. För närvarande innebär det att anropa ToString innan du skickar till StringBuilder.Append.

Om rekordtypen härleds från en basrekord Base, innehåller rekordet en syntetiskt överskrivning som motsvarar en metod som deklareras enligt följande:

protected override bool PrintMembers(StringBuilder builder);

Om posten inte har några utskrivbara medlemmar anropar metoden metoden base PrintMembers med ett argument (dess builder parameter) och returnerar resultatet.

I annat fall metoden:

  1. anropar metoden base PrintMembers med ett argument (dess builder parameter).
  2. Om metoden PrintMembers returnerade true, lägg till ", " till strängbyggaren.
  3. för var och en av postens utskrivbara medlemmar lägger till medlemmens namn följt av " = " följt av medlemmens värde: this.member (eller this.member.ToString() för värdetyper), avgränsade med ", ",
  4. returnera sant.

Metoden PrintMembers kan deklareras explicit. Det är ett fel om den explicita deklarationen inte matchar den förväntade signaturen eller tillgängligheten, eller om den explicita deklarationen inte tillåter åsidosättande av den i en härledd typ och posttypen inte är sealed.

Posten innehåller en syntetiserad metod som motsvarar en metod som deklareras enligt följande:

public override string ToString();

Metoden kan deklareras tydligt. Det är ett fel om den explicita deklarationen inte matchar den förväntade signaturen eller tillgängligheten, eller om den explicita deklarationen inte tillåter åsidosättande av den i en härledd typ och posttypen inte är sealed. Det är ett fel om antingen syntetiserad eller explicit deklarerad metod inte åsidosätter object.ToString() (till exempel på grund av skuggning i mellanliggande bastyper osv.).

Den syntetiserade metoden:

  1. skapar en StringBuilder instans,
  2. lägger till postnamnet i byggaren följt av " { ",
  3. anropar postens PrintMembers-metod som ger den byggaren, följt av " " " om den returnerade true,
  4. lägger till "}"
  5. returnerar byggares innehåll med builder.ToString().

Tänk till exempel på följande posttyper:

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

För dessa posttyper skulle de syntetiserade utskriftsmedlemmarna vara ungefär så här:

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

Positionella postmedlemmar

Förutom ovanstående medlemmar syntetiserar poster med en parameterlista ("positionella poster") ytterligare medlemmar med samma villkor som medlemmarna ovan.

Primär konstruktor

En posttyp har en offentlig konstruktor vars signatur motsvarar värdeparametrarna i typdeklarationen. Detta kallas den primära konstruktorn för typen och gör att den implicit deklarerade standardklasskonstruktorn, om den finns, utelämnas. Det är ett fel att ha både en primär konstruktor och en konstruktor med samma signatur som redan är närvarande i klassen.

Vid körningen var den primära konstruktorn

  1. kör instansinitierarna som visas i klasskroppen

  2. anropar basklasskonstruktorn med de argument som anges i record_base-satsen, om det finns

Om en post har en primär konstruktor måste alla användardefinierade konstruktorer, förutom en "kopieringskonstruktor", ha en explicit konstruktorinitierare this.

Parametrar för den primära konstruktorn samt medlemmar i rekorden är i scope inom argument_list av record_base-villkoret och inom initialiserare av instansfält eller egenskaper. Instansmedlemmar skulle orsaka ett fel i dessa sammanhang (likt hur de är i omfånget i vanliga konstruktorinitierare idag, men med ett sammanhangsbaserat fel vid användning), men parametrarna för den primära konstruktorn skulle vara i omfång, användbara och överskugga medlemmar. Statiska medlemmar skulle också vara användbara, ungefär som basanrop och initierare fungerar i vanliga konstruktorer idag.

En varning genereras om en parameter för den primära konstruktorn inte läss.

Uttrycksvariabler som deklareras i argument_list är tillgängliga inom argument_list. Samma skuggningsregler som i en argumentlista med en vanlig konstruktorinitierare gäller.

Egenskaper

För varje postparameter i en deklaration av en posttyp finns det en motsvarande offentlig egenskapsmedlem vars namn och typ hämtas från deklarationen av värdeparameter.

För ett protokoll:

  • En offentlig get och init automatisk egenskap skapas (se separat init accessorspecifikation). En ärvd abstract-egenskap med matchande typ åsidosätts. Det är ett fel om den ärvda egenskapen inte har åsidosättande public samt get- och init-accessorer. Det är ett fel om den ärvda egenskapen är dold.
    Den automatiska egenskapen initieras med värdet av den motsvarande primära konstruktorns parameter. Attribut kan tillämpas på den syntetiserade autoegenskapen och dess bakgrundsfält med hjälp av property: eller field: mål för attribut som syntaktiskt tillämpas på motsvarande postparameter.

Dekonstruera

En positionell post med minst en parameter syntetiserar en offentlig void-returning-instansmetod med namnet Deconstruct med en utparameterdeklaration för varje parameter i den primära konstruktordeklarationen. Varje parameter i metoden Deconstruct har samma typ som motsvarande parameter i den primära konstruktordeklarationen. Metoden tilldelar varje parameter i metoden Deconstruct dess motsvarande instansegenskaps värde. Metoden kan deklareras explicit. Det är ett fel om den explicita deklarationen inte matchar den förväntade signaturen eller tillgängligheten, eller om den är statisk.

I följande exempel visas en positionell post R med dess kompilator syntetiserade Deconstruct metod, tillsammans med dess användning:

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 uttryck

Ett with uttryck är ett nytt uttryck med hjälp av följande syntax.

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

member_initializer_list
    : member_initializer (',' member_initializer)*
    ;

member_initializer
    : identifier '=' expression
    ;

Ett with uttryck tillåts inte som en sats.

Ett with uttryck möjliggör "icke-destruktiv mutation", som utformats för att producera en kopia av mottagaruttrycket med ändringar i tilldelningar i member_initializer_list.

Ett giltigt with-uttryck har en mottagare med en typ som inte är void. Mottagartypen måste vara en post.

Till höger i with-uttrycket finns en member_initializer_list med en sekvens med tilldelningar till identifieraren, som måste vara ett tillgängligt instansfält eller en egenskap av typen av mottagaren.

Först anropas mottagarens "klon"-metod (anges ovan) och resultatet konverteras till mottagarens typ. Sedan bearbetas varje member_initializer på samma sätt som en tilldelning till ett fält eller en egenskapsåtkomst till resultatet av konverteringen. Uppgifter hanteras i lexikografisk ordning.