Dela via


Poster (C#-referens)

Du använder record modifieraren för att definiera en referenstyp som tillhandahåller inbyggda funktioner för att kapsla in data. Med C# 10 kan syntaxen record class som synonym klargöra en referenstyp och record struct definiera en värdetyp med liknande funktioner.

När du deklarerar en primär konstruktor på en post genererar kompilatorn offentliga egenskaper för de primära konstruktorparametrarna. De primära konstruktorparametrarna till en post kallas för positionsparametrar. Kompilatorn skapar positionsegenskaper som speglar den primära konstruktorn eller positionsparametrarna. Kompilatorn syntetiserar inte egenskaper för primära konstruktorparametrar för typer som inte har record modifieraren.

Följande två exempel visar record (eller record class) referenstyper:

public record Person(string FirstName, string LastName);
public record Person
{
    public required string FirstName { get; init; }
    public required string LastName { get; init; }
};

Följande två exempel visar record struct värdetyper:

public readonly record struct Point(double X, double Y, double Z);
public record struct Point
{
    public double X { get; init; }
    public double Y { get; init; }
    public double Z { get; init; }
}

Du kan också skapa poster med föränderliga egenskaper och fält:

public record Person
{
    public required string FirstName { get; set; }
    public required string LastName { get; set; }
};

Post structs kan också vara föränderliga, både positionella post structs och post structs utan positionsparametrar:

public record struct DataMeasurement(DateTime TakenAt, double Measurement);
public record struct Point
{
    public double X { get; set; }
    public double Y { get; set; }
    public double Z { get; set; }
}

Även om poster kan vara föränderliga är de främst avsedda att stödja oföränderliga datamodeller. Posttypen erbjuder följande funktioner:

I föregående exempel visas några skillnader mellan poster som är referenstyper och poster som är värdetyper:

  • En record eller en record class deklarerar en referenstyp. Nyckelordet class är valfritt, men kan öka tydligheten för läsarna. En record struct deklarerar en värdetyp.
  • Positionella egenskaper är oföränderliga i en record class och en readonly record struct. De är föränderliga i en record struct.

Resten av den här artikeln beskriver både record class och record struct typer. Skillnaderna beskrivs i varje avsnitt. Du bör välja mellan en record class och en record struct som liknar att bestämma mellan en class och en struct. Termen post används för att beskriva beteende som gäller för alla posttyper. Antingen record struct eller record class används för att beskriva beteende som endast gäller för struct- eller klasstyper. Typen record struct introducerades i C# 10.

Positionssyntax för egenskapsdefinition

Du kan använda positionsparametrar för att deklarera egenskaper för en post och initiera egenskapsvärdena när du skapar en instans:

public record Person(string FirstName, string LastName);

public static void Main()
{
    Person person = new("Nancy", "Davolio");
    Console.WriteLine(person);
    // output: Person { FirstName = Nancy, LastName = Davolio }
}

När du använder positionssyntaxen för egenskapsdefinition skapar kompilatorn:

  • En offentlig autoimplementerad egenskap för varje positionsparameter som anges i postdeklarationen.
    • För record typer och readonly record struct typer: En init-only-egenskap .
    • För record struct typer: En skrivskyddad egenskap.
  • En primär konstruktor vars parametrar matchar positionsparametrarna i postdeklarationen.
  • För poststruktureringstyper är en parameterlös konstruktor som anger varje fält till standardvärdet.
  • En Deconstruct metod med en out parameter för varje positionsparameter som anges i postdeklarationen. Metoden dekonstruerar egenskaper som definieras med hjälp av positionssyntax. den ignorerar egenskaper som definieras med hjälp av standardegenskapssyntax.

Du kanske vill lägga till attribut till något av de här elementen som kompilatorn skapar från postdefinitionen. Du kan lägga till ett mål för alla attribut som du tillämpar på positionspostens egenskaper. Följande exempel gäller System.Text.Json.Serialization.JsonPropertyNameAttribute för varje egenskap för Person posten. Målet property: anger att attributet tillämpas på den kompilatorgenererade egenskapen. Andra värden är field: att tillämpa attributet på fältet och param: tillämpa attributet på parametern.

/// <summary>
/// Person record type
/// </summary>
/// <param name="FirstName">First Name</param>
/// <param name="LastName">Last Name</param>
/// <remarks>
/// The person type is a positional record containing the
/// properties for the first and last name. Those properties
/// map to the JSON elements "firstName" and "lastName" when
/// serialized or deserialized.
/// </remarks>
public record Person([property: JsonPropertyName("firstName")] string FirstName, 
    [property: JsonPropertyName("lastName")] string LastName);

I föregående exempel visas också hur du skapar XML-dokumentationskommentarer för posten. Du kan lägga till taggen <param> för att lägga till dokumentation för den primära konstruktorns parametrar.

Om den genererade autoimplementerade egenskapsdefinitionen inte är det du vill ha kan du definiera din egen egenskap med samma namn. Du kanske till exempel vill ändra hjälpmedel eller föränderlighet, eller tillhandahålla en implementering för antingen get eller set -accessorn. Om du deklarerar egenskapen i källan måste du initiera den från postens positionsparameter. Om din egenskap är en autoimplementerad egenskap måste du initiera egenskapen. Om du lägger till ett bakgrundsfält i källan måste du initiera bakgrundsfältet. Den genererade dekonstruktionen använder din egenskapsdefinition. I följande exempel deklareras FirstName till exempel egenskaperna och LastName för en positionell post public, men positionsparametern begränsas Id till internal. Du kan använda den här syntaxen för poster och poststruktureringstyper.

public record Person(string FirstName, string LastName, string Id)
{
    internal string Id { get; init; } = Id;
}

public static void Main()
{
    Person person = new("Nancy", "Davolio", "12345");
    Console.WriteLine(person.FirstName); //output: Nancy

}

En posttyp behöver inte deklarera några positionsegenskaper. Du kan deklarera en post utan några positionsegenskaper och du kan deklarera andra fält och egenskaper, som i följande exempel:

public record Person(string FirstName, string LastName)
{
    public string[] PhoneNumbers { get; init; } = [];
};

Om du definierar egenskaper med hjälp av standardegenskapssyntaxen men utelämnar åtkomstmodifieraren är egenskaperna implicit private.

Oföränderlighet

En positionell post och en positionell skrivskyddad post struct deklarerar init-only-egenskaper. En positionell poststruct deklarerar skrivskyddade egenskaper. Du kan åsidosätta något av dessa standardvärden, som du ser i föregående avsnitt.

Oföränderlighet kan vara användbart när du behöver en datacentrerad typ för att vara trådsäker eller om du är beroende av att en hashkod förblir densamma i en hash-tabell. Oföränderlighet är dock inte lämpligt för alla datascenarier. Entity Framework Core stöder till exempel inte uppdatering med oföränderliga entitetstyper.

Egenskaper med enbart init, oavsett om de har skapats från positionsparametrar (record classoch ) eller genom att init ange åtkomst, har ytlig oföränderlighetreadonly record struct. Efter initieringen kan du inte ändra värdet för egenskaper av värdetyp eller referensen för egenskaper av referenstyp. De data som en referenstypsegenskap refererar till kan dock ändras. I följande exempel visas att innehållet i en oföränderlig egenskap av referenstyp (en matris i det här fallet) kan ändras:

public record Person(string FirstName, string LastName, string[] PhoneNumbers);

public static void Main()
{
    Person person = new("Nancy", "Davolio", new string[1] { "555-1234" });
    Console.WriteLine(person.PhoneNumbers[0]); // output: 555-1234

    person.PhoneNumbers[0] = "555-6789";
    Console.WriteLine(person.PhoneNumbers[0]); // output: 555-6789
}

Funktionerna som är unika för posttyper implementeras med kompilatorsyntetiserade metoder, och ingen av dessa metoder äventyrar oföränderligheten genom att ändra objekttillståndet. Om inte anges genereras de syntetiserade metoderna för record, record structoch readonly record struct deklarationer.

Värdejämlikhet

Om du inte åsidosätter eller ersätter likhetsmetoder styr typen du deklarerar hur likhet definieras:

  • För class typer är två objekt lika om de refererar till samma objekt i minnet.
  • För struct typer är två objekt lika om de är av samma typ och lagrar samma värden.
  • För typer med record modifieraren (record class, record structoch readonly record struct) är två objekt lika om de är av samma typ och lagrar samma värden.

Definitionen av likhet för en record struct är densamma som för en struct. Skillnaden är att för en structär implementeringen i ValueType.Equals(Object) och förlitar sig på reflektion. För poster syntetiseras implementeringen av implementeringen och de deklarerade datamedlemmarna används.

Referensjämlikhet krävs för vissa datamodeller. Entity Framework Core är till exempel beroende av referensjämlikhet för att säkerställa att den endast använder en instans av en entitetstyp för vad som konceptuellt är en entitet. Därför är poster och poststrukturer inte lämpliga för användning som entitetstyper i Entity Framework Core.

I följande exempel visas värdejämlikhet för posttyper:

public record Person(string FirstName, string LastName, string[] PhoneNumbers);

public static void Main()
{
    var phoneNumbers = new string[2];
    Person person1 = new("Nancy", "Davolio", phoneNumbers);
    Person person2 = new("Nancy", "Davolio", phoneNumbers);
    Console.WriteLine(person1 == person2); // output: True

    person1.PhoneNumbers[0] = "555-1234";
    Console.WriteLine(person1 == person2); // output: True

    Console.WriteLine(ReferenceEquals(person1, person2)); // output: False
}

För att implementera värdejämlikhet syntetiserar kompilatorn flera metoder, bland annat:

  • En åsidosättning av Object.Equals(Object). Det är ett fel om åsidosättningen deklareras explicit.

    Den här metoden används som grund för den Object.Equals(Object, Object) statiska metoden när båda parametrarna inte är null.

  • En virtual, eller sealed, Equals(R? other) där R är posttypen. Den här metoden implementerar IEquatable<T>. Den här metoden kan deklareras explicit.

  • Om posttypen härleds från en basposttyp Base, . Equals(Base? other) Det är ett fel om åsidosättningen deklareras explicit. Om du tillhandahåller din egen implementering av Equals(R? other)tillhandahåller du även en implementering av GetHashCode .

  • En åsidosättning av Object.GetHashCode(). Den här metoden kan deklareras explicit.

  • Åsidosättningar av operatorer == och !=. Det är ett fel om operatorerna deklareras explicit.

  • Om posttypen härleds från en basposttyp, protected override Type EqualityContract { get; };. Den här egenskapen kan deklareras explicit. Mer information finns i Likhet i arvshierarkier.

Kompilatorn syntetiserar inte en metod när en posttyp har en metod som matchar signaturen för en syntetiserad metod som tillåts deklareras explicit.

Icke-förstörande mutation

Om du behöver kopiera en instans med vissa ändringar kan du använda ett with uttryck för att uppnå icke-förstörande mutation. Ett with uttryck gör en ny postinstans som är en kopia av en befintlig postinstans med angivna egenskaper och fält ändrade. Du använder syntaxen för initiering av objekt för att ange de värden som ska ändras, enligt följande exempel:

public record Person(string FirstName, string LastName)
{
    public string[] PhoneNumbers { get; init; }
}

public static void Main()
{
    Person person1 = new("Nancy", "Davolio") { PhoneNumbers = new string[1] };
    Console.WriteLine(person1);
    // output: Person { FirstName = Nancy, LastName = Davolio, PhoneNumbers = System.String[] }

    Person person2 = person1 with { FirstName = "John" };
    Console.WriteLine(person2);
    // output: Person { FirstName = John, LastName = Davolio, PhoneNumbers = System.String[] }
    Console.WriteLine(person1 == person2); // output: False

    person2 = person1 with { PhoneNumbers = new string[1] };
    Console.WriteLine(person2);
    // output: Person { FirstName = Nancy, LastName = Davolio, PhoneNumbers = System.String[] }
    Console.WriteLine(person1 == person2); // output: False

    person2 = person1 with { };
    Console.WriteLine(person1 == person2); // output: True
}

Uttrycket with kan ange positionsegenskaper eller egenskaper som skapas med hjälp av standardegenskapssyntax. Uttryckligen deklarerade egenskaper måste ha en init eller-accessor set som ska ändras i ett with uttryck.

Resultatet av ett with uttryck är en ytlig kopia, vilket innebär att för en referensegenskap kopieras endast referensen till en instans. Både den ursprungliga posten och kopian får en referens till samma instans.

För att implementera den här funktionen för record class typer syntetiserar kompilatorn en klonmetod och en kopieringskonstruktor. Metoden virtuell klon returnerar en ny post som initierats av kopieringskonstruktorn. När du använder ett with uttryck skapar kompilatorn kod som anropar klonmetoden och anger sedan de egenskaper som anges i with uttrycket.

Om du behöver olika kopieringsbeteende kan du skriva en egen kopieringskonstruktor i en record class. Om du gör det syntetiserar inte kompilatorn en. Gör konstruktorn private om posten är sealed, annars gör du den protectedtill . Kompilatorn syntetiserar inte någon kopieringskonstruktor för record struct typer. Du kan skriva en, men kompilatorn genererar inte anrop till den för with uttryck. Värdena för record struct kopieras vid tilldelning.

Du kan inte åsidosätta klonmetoden och du kan inte skapa en medlem med namnet Clone i någon posttyp. Det faktiska namnet på klonmetoden genereras av kompilatorn.

Inbyggd formatering för visning

Posttyper har en kompilatorgenererad ToString metod som visar namn och värden för offentliga egenskaper och fält. Metoden ToString returnerar en sträng med följande format:

<posttypsnamn> { <egenskapsnamn> = <värde>, <egenskapsnamn> = <värde>, ...}

Strängen som skrivs ut för <value> är strängen ToString() som returneras av för egenskapens typ. I följande exempel ChildNames är en System.Array, där ToString returnerar System.String[]:

Person { FirstName = Nancy, LastName = Davolio, ChildNames = System.String[] }

För att implementera den här funktionen syntetiserar kompilatorn i record class typer en virtuell PrintMembers metod och en ToString åsidosättning. I record struct typer är privateden här medlemmen . Åsidosättningen ToString skapar ett StringBuilder objekt med typnamnet följt av en inledande hakparentes. Den anropar PrintMembers för att lägga till egenskapsnamn och värden och lägger sedan till den avslutande hakparentesen. I följande exempel visas kod som liknar vad den syntetiserade åsidosättningen innehåller:

public override string ToString()
{
    StringBuilder stringBuilder = new StringBuilder();
    stringBuilder.Append("Teacher"); // type name
    stringBuilder.Append(" { ");
    if (PrintMembers(stringBuilder))
    {
        stringBuilder.Append(" ");
    }
    stringBuilder.Append("}");
    return stringBuilder.ToString();
}

Du kan ange din egen implementering av PrintMembers eller åsidosättningen ToString . Exempel finns i PrintMembers avsnittet formatering i härledda poster senare i den här artikeln. I C# 10 och senare kan implementeringen av ToString inkludera sealed modifieraren, vilket hindrar kompilatorn från att syntetisera en ToString implementering för härledda poster. Du kan skapa en konsekvent strängrepresentation i en hierarki av record typer. (Härledda poster har fortfarande en PrintMembers metod som genereras för alla härledda egenskaper.)

Arv

Det här avsnittet gäller endast för record class typer.

En post kan ärva från en annan post. En post kan dock inte ärva från en klass och en klass kan inte ärva från en post.

Positionsparametrar i härledda posttyper

Den härledda posten deklarerar positionsparametrar för alla parametrar i den primära konstruktorn för basposten. Basposten deklarerar och initierar dessa egenskaper. Den härledda posten döljer dem inte, utan skapar och initierar bara egenskaper för parametrar som inte deklareras i basposten.

I följande exempel visas arv med syntax för positionsegenskap:

public abstract record Person(string FirstName, string LastName);
public record Teacher(string FirstName, string LastName, int Grade)
    : Person(FirstName, LastName);
public static void Main()
{
    Person teacher = new Teacher("Nancy", "Davolio", 3);
    Console.WriteLine(teacher);
    // output: Teacher { FirstName = Nancy, LastName = Davolio, Grade = 3 }
}

Likhet i arvshierarkier

Det här avsnittet gäller för record class typer, men inte record struct typer. För att två postvariabler ska vara lika med måste körningstypen vara lika med. De typer av variabler som innehåller kan vara olika. Jämförelse av ärvd likhet illustreras i följande kodexempel:

public abstract record Person(string FirstName, string LastName);
public record Teacher(string FirstName, string LastName, int Grade)
    : Person(FirstName, LastName);
public record Student(string FirstName, string LastName, int Grade)
    : Person(FirstName, LastName);
public static void Main()
{
    Person teacher = new Teacher("Nancy", "Davolio", 3);
    Person student = new Student("Nancy", "Davolio", 3);
    Console.WriteLine(teacher == student); // output: False

    Student student2 = new Student("Nancy", "Davolio", 3);
    Console.WriteLine(student2 == student); // output: True
}

I exemplet deklareras alla variabler som Person, även om instansen är en härledd typ av antingen Student eller Teacher. Instanserna har samma egenskaper och samma egenskapsvärden. Men student == teacher returnerar False även om båda är Person-type-variabler och student == student2 returnerar True även om en är en Person variabel och en är en Student variabel. Likhetstestet beror på körningstypen för det faktiska objektet, inte variabelns deklarerade typ.

För att implementera det här beteendet syntetiserar kompilatorn en EqualityContract egenskap som returnerar ett Type objekt som matchar postens typ. EqualityContract Gör det möjligt för likhetsmetoderna att jämföra körningstypen för objekt när de söker efter likhet. Om bastypen för en post är objectär virtualden här egenskapen . Om bastypen är en annan posttyp är den här egenskapen en åsidosättning. Om posttypen är sealedberor den här egenskapen effektivt sealed på att typen är sealed.

När koden jämför två instanser av en härledd typ kontrollerar de syntetiserade likhetsmetoderna alla datamedlemmar i bas- och härledda typer för likhet. Den syntetiserade GetHashCode metoden använder GetHashCode metoden från alla datamedlemmar som deklarerats i bastypen och den härledda posttypen. Datamedlemmarna i ett record inkluderar alla deklarerade fält och det kompilatorsyntetiserade bakgrundsfältet för eventuella autoimplementerade egenskaper.

with uttryck i härledda poster

Resultatet av ett with uttryck har samma körningstyp som uttryckets operand. Alla egenskaper för körningstypen kopieras, men du kan bara ange egenskaper för kompileringstidstypen, som följande exempel visar:

public record Point(int X, int Y)
{
    public int Zbase { get; set; }
};
public record NamedPoint(string Name, int X, int Y) : Point(X, Y)
{
    public int Zderived { get; set; }
};

public static void Main()
{
    Point p1 = new NamedPoint("A", 1, 2) { Zbase = 3, Zderived = 4 };

    Point p2 = p1 with { X = 5, Y = 6, Zbase = 7 }; // Can't set Name or Zderived
    Console.WriteLine(p2 is NamedPoint);  // output: True
    Console.WriteLine(p2);
    // output: NamedPoint { X = 5, Y = 6, Zbase = 7, Name = A, Zderived = 4 }

    Point p3 = (NamedPoint)p1 with { Name = "B", X = 5, Y = 6, Zbase = 7, Zderived = 8 };
    Console.WriteLine(p3);
    // output: NamedPoint { X = 5, Y = 6, Zbase = 7, Name = B, Zderived = 8 }
}

PrintMembers formatering i härledda poster

Den syntetiserade metoden av en härledd PrintMembers posttyp anropar basimplementeringen. Resultatet är att alla offentliga egenskaper och fält för både härledda och grundläggande typer ingår i ToString utdata, som du ser i följande exempel:

public abstract record Person(string FirstName, string LastName);
public record Teacher(string FirstName, string LastName, int Grade)
    : Person(FirstName, LastName);
public record Student(string FirstName, string LastName, int Grade)
    : Person(FirstName, LastName);

public static void Main()
{
    Person teacher = new Teacher("Nancy", "Davolio", 3);
    Console.WriteLine(teacher);
    // output: Teacher { FirstName = Nancy, LastName = Davolio, Grade = 3 }
}

Du kan ange din egen implementering av PrintMembers metoden. Om du gör det använder du följande signatur:

  • För en sealed post som härleds från object (deklarerar inte en baspost): private bool PrintMembers(StringBuilder builder);
  • För en sealed post som härleds från en annan post (observera att omslutningstypen är sealed, så metoden är effektivt sealed): protected override bool PrintMembers(StringBuilder builder);
  • För en post som inte sealed är och härleds från objektet: protected virtual bool PrintMembers(StringBuilder builder);
  • För en post som inte sealed är och härleds från en annan post: protected override bool PrintMembers(StringBuilder builder);

Här är ett exempel på kod som ersätter de syntetiserade PrintMembers metoderna, en för en posttyp som härleds från objektet och en för en posttyp som härleds från en annan post:

public abstract record Person(string FirstName, string LastName, string[] PhoneNumbers)
{
    protected virtual bool PrintMembers(StringBuilder stringBuilder)
    {
        stringBuilder.Append($"FirstName = {FirstName}, LastName = {LastName}, ");
        stringBuilder.Append($"PhoneNumber1 = {PhoneNumbers[0]}, PhoneNumber2 = {PhoneNumbers[1]}");
        return true;
    }
}

public record Teacher(string FirstName, string LastName, string[] PhoneNumbers, int Grade)
    : Person(FirstName, LastName, PhoneNumbers)
{
    protected override bool PrintMembers(StringBuilder stringBuilder)
    {
        if (base.PrintMembers(stringBuilder))
        {
            stringBuilder.Append(", ");
        };
        stringBuilder.Append($"Grade = {Grade}");
        return true;
    }
};

public static void Main()
{
    Person teacher = new Teacher("Nancy", "Davolio", new string[2] { "555-1234", "555-6789" }, 3);
    Console.WriteLine(teacher);
    // output: Teacher { FirstName = Nancy, LastName = Davolio, PhoneNumber1 = 555-1234, PhoneNumber2 = 555-6789, Grade = 3 }
}

Kommentar

I C# 10 och senare syntetiserar PrintMembers kompilatorn i härledda poster även när en baspost har förseglat ToString metoden. Du kan också skapa en egen implementering av PrintMembers.

Dekonstruktionsbeteende i härledda poster

Metoden Deconstruct för en härledd post returnerar värdena för alla positionella egenskaper för kompileringstidstypen. Om variabeltypen är en baspost dekonstrueras endast baspostegenskaperna såvida inte objektet skickas till den härledda typen. I följande exempel visas hur du anropar en dekonstruktion på en härledd post.

public abstract record Person(string FirstName, string LastName);
public record Teacher(string FirstName, string LastName, int Grade)
    : Person(FirstName, LastName);
public record Student(string FirstName, string LastName, int Grade)
    : Person(FirstName, LastName);

public static void Main()
{
    Person teacher = new Teacher("Nancy", "Davolio", 3);
    var (firstName, lastName) = teacher; // Doesn't deconstruct Grade
    Console.WriteLine($"{firstName}, {lastName}");// output: Nancy, Davolio

    var (fName, lName, grade) = (Teacher)teacher;
    Console.WriteLine($"{fName}, {lName}, {grade}");// output: Nancy, Davolio, 3
}

Allmänna begränsningar

Nyckelordet record är en modifierare för antingen en class eller struct en typ. record När du lägger till modifieraren ingår det beteende som beskrivs tidigare i den här artikeln. Det finns ingen allmän begränsning som kräver att en typ är en post. A record class uppfyller villkoret class . A record struct uppfyller villkoret struct . Mer information finns i Begränsningar för typparametrar.

Språkspecifikation för C#

Mer information finns i avsnittet Klasser i C#-språkspecifikationen.

Mer information om dessa funktioner finns i följande kommentarer om funktionsförslag:

Se även