Záznamy (Referenční dokumentace jazyka C#)
Počínaje jazykem C# 9 použijete record klíčové slovo k definování typu odkazu , který poskytuje integrovanou funkci pro zapouzdření dat. Typy záznamů s neměnnými vlastnostmi můžete vytvořit pomocí pozičních parametrů nebo syntaxe standardní vlastnosti:
public record Person(string FirstName, string LastName);
public record Person
{
public string FirstName { get; init; } = default!;
public string LastName { get; init; } = default!;
};
Můžete také vytvořit typy záznamů s proměnlivými vlastnostmi a poli:
public record Person
{
public string FirstName { get; set; } = default!;
public string LastName { get; set; } = default!;
};
I když záznamy mohou být proměnlivé, jsou primárně určeny pro podporu neměnných datových modelů. Typ záznamu nabízí tyto funkce:
- Stručná syntaxe pro vytvoření typu odkazu s neproměnlivými vlastnostmi
- Předdefinované chování užitečné pro typ odkazu orientovaný na data:
- Podpora hierarchií dědičnosti
Můžete také použít typy struktury k návrhu typů orientovaných na data, které poskytují rovnost hodnot a minimální chování. V C# 10 a novějších můžete definovat record struct typy pomocí buď pozičních parametrů, nebo syntaxe standardní vlastnosti:
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; }
}
Struktury záznamů můžou být proměnlivé i struktury umístění záznamů a struktury záznamů bez pozičních parametrů:
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; }
}
Předchozí příklady ukazují některé rozdíly mezi záznamy, které jsou odkazové typy a záznamy, které jsou typy hodnot:
recordNeborecord classdeklaruje typ odkazu.classKlíčové slovo je volitelné, ale může pro čtenáře přidat přehlednost.record structDeklaruje typ hodnoty.- Poziční vlastnosti jsou neměnné v
record classareadonly record struct. Jsou proměnlivé vrecord struct.
Zbývající část tohoto článku popisuje oba record class typy i record struct . Rozdíly jsou podrobně popsané v jednotlivých oddílech. Měli byste se rozhodnout mezi a record class a record struct podobným rozhodnutím mezi class a struct . Záznam termínu se používá k popisu chování, které platí pro všechny typy záznamů. Buď record struct nebo record class se používá k popisu chování, které se vztahuje pouze na typy struktury nebo třídy, v uvedeném pořadí.
Poziční syntaxe pro definici vlastnosti
Poziční parametry můžete použít k deklarování vlastností záznamu a k inicializaci hodnot vlastností při vytváření instance:
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 }
}
Použijete-li syntaxi pozice pro definici vlastnosti, kompilátor vytvoří:
- Veřejná vlastnost s automatickou implementací pro každý poziční parametr poskytnutý v deklaraci záznamu.
- Pro
recordtypy areadonly record structtypy: vlastnost pouze pro inicializaci lze nastavit pouze v konstruktoru nebo pomocí inicializátoru vlastnosti. - Pro
record structtypy: vlastnost pro čtení i zápis, která se dá nastavit v konstruktoru, inicializátoru vlastnosti nebo přiřazení po konstrukci.
- Pro
- Primární konstruktor, jehož parametry odpovídají pozičním parametrům v deklaraci záznamu.
- Pro typy struktur záznamu, konstruktor bez parametrů, který nastaví každé pole na jeho výchozí hodnotu.
DeconstructMetoda soutparametrem pro každý poziční parametr poskytnutý v deklaraci záznamu. Tato metoda je k dispozici pouze v případě, že existují dva nebo více pozičních parametrů. Metoda dekonstruuje vlastnosti definované pomocí poziční syntaxe; ignoruje vlastnosti, které jsou definovány pomocí syntaxe standardních vlastností.
Můžete chtít přidat atributy k jakémukoli z těchto prvků, které kompilátor vytvoří z definice záznamu. Cíl můžete přidat do libovolného atributu, který použijete u vlastností pozičního záznamu. Následující příklad platí System.Text.Json.Serialization.JsonPropertyNameAttribute pro každou vlastnost Person záznamu. property:Cíl označuje, že atribut je použit pro vlastnost generovanou kompilátorem. Další hodnoty jsou field: použity pro použití atributu na pole a param: pro použití atributu na parametr.
/// <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);
Předchozí příklad také ukazuje, jak vytvořit dokumentační komentáře XML pro záznam. Můžete přidat značku pro <param> Přidání dokumentace k parametrům primárního konstruktoru.
Pokud vygenerovaná definice automaticky implementované vlastnosti není tak, jak chcete, můžete definovat vlastní vlastnost se stejným názvem. Pokud to uděláte, vygenerovaný konstruktor a dekonstruktor budou používat vaši definici vlastnosti. Například následující příklad deklaruje FirstName LastName vlastnosti a pozičního záznamu public , ale omezuje Id poziční parametr na internal . Tuto syntaxi můžete použít pro záznamy a typy struktury záznamů. Je nutné přidat explicitní přiřazení deklarované vlastnosti k odpovídajícímu pozičnímu parametru.
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
}
Typ záznamu nemusí deklarovat žádné poziční vlastnosti. Můžete deklarovat záznam bez pozičních vlastností a můžete deklarovat další pole a vlastnosti, jako v následujícím příkladu:
public record Person(string FirstName, string LastName)
{
public string[] PhoneNumbers { get; init; } = Array.Empty<string>();
};
Pokud definujete vlastnosti pomocí syntaxe standardní vlastnosti, ale vynecháte modifikátor přístupu, vlastnosti jsou implicitně private .
Neměnnost
Poziční záznam a pozice pro strukturu záznamu jen pro čtení deklaruje vlastnosti pouze pro inicializaci. Poziční struktura záznamu deklaruje vlastnosti pro čtení i zápis. Některé z těchto výchozích hodnot můžete přepsat, jak je znázorněno v předchozí části.
Neměnnosti může být užitečné, když potřebujete datový typ, který je bezpečný pro přístup z více vláken nebo v závislosti na hodnotě hash zbylé stejné v zatřiďovací tabulce. Neměnnosti se ale nehodí pro všechny scénáře dat. Entity Framework Corenapříklad nepodporuje aktualizaci s neměnnými typy entit.
Vlastnosti pouze pro inicializaci, ať už vytvořené z pozičních parametrů (a record class readonly record struct ) nebo určením init přístupových objektů, mají omezený neměnnosti. Po inicializaci nemůžete změnit hodnotu vlastnosti typu hodnoty nebo odkaz na vlastnosti referenčního typu. Data, která odkazuje na vlastnost referenčního typu, však lze změnit. Následující příklad ukazuje, že obsah vlastnosti neměnný typ odkazu (pole v tomto případě) je proměnlivý:
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
}
Funkce, které jsou jedinečné pro typy záznamů, jsou implementovány metodami syntetizované kompilátorem a žádná z těchto metod neohrožuje neměnnosti úpravou stavu objektu. Pokud není zadán, vygenerují se syntetizované metody record pro record struct deklarace, a readonly record struct .
Hodnota rovnosti
Pro libovolný typ, který definujete, můžete přepsat Object.Equals(Object) a přetížit operator == . Pokud nepřepíšete Equals nebo operator == nepřevedete, typ, který deklarujete, určuje, jak je definována rovnost:
- U
classtypů jsou dva objekty stejné, pokud odkazují na stejný objekt v paměti. - U
structtypů jsou dva objekty stejné, pokud jsou stejného typu a ukládají stejné hodnoty. - Pro
recordtypy, včetněrecord structareadonly record struct, jsou dva objekty stejné, pokud jsou stejného typu a ukládají stejné hodnoty.
Definice rovnosti pro a record struct je stejná jako pro struct . Rozdíl je v tom, že struct implementace je v ValueType.Equals(Object) a spoléhá na reflexi. U záznamů je implementace kompilátorem syntetizovaná a používá deklarované datové členy.
Pro některé datové modely je vyžadována referenční rovnost. Například Entity Framework Core závisí na rovnosti referencí, aby se zajistilo, že používá pouze jednu instanci typu entity pro to, co je koncepčně jedna entita. Z tohoto důvodu nejsou záznamy a struktury záznamů vhodné pro použití jako typy entit v Entity Framework Core.
Následující příklad znázorňuje hodnotu rovnosti typů záznamů:
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
}
Pro implementaci rovnosti hodnot kompilátor syntetizuje následující metody:
Přepsání Object.Equals(Object) .
Tato metoda se používá jako základ pro Object.Equals(Object, Object) statickou metodu, pokud jsou oba parametry jiné než null.
Virtuální
Equalsmetoda, jejíž parametr je typu záznamu. Tato metoda implementuje IEquatable<T> .Přepsání Object.GetHashCode() .
Přepisuje operátory
==a!=.
Můžete napsat vlastní implementace, které nahradí některou z těchto syntetizních metod. Pokud typ záznamu obsahuje metodu, která odpovídá signatuře jakékoli syntetizované metody, kompilátor tuto metodu nesyntetizuje.
Pokud zadáte vlastní implementaci Equals v typu záznamu, zajistěte implementaci GetHashCode také.
Nedestruktivní mutace
Pokud potřebujete zkopírovat instanci s některými úpravami, můžete použít with výraz k dosažení nedestruktivních mutací. withVýraz vytvoří novou instanci záznamu, která je kopií existující instance záznamu se zadanými vlastnostmi a poli změněnými. Pomocí syntaxe inicializátoru objektů určíte hodnoty, které mají být změněny, jak je znázorněno v následujícím příkladu:
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
}
withVýraz může nastavit poziční vlastnosti nebo vlastnosti vytvořené pomocí syntaxe standardních vlastností. Nepoziční vlastnosti musí mít init set ve výrazu změnu typu objektu nebo with .
Výsledek with výrazu je podmnožina bez podstruktury, což znamená, že pro vlastnost reference se kopíruje pouze odkaz na instanci. Původní záznam i jeho kopie končí odkazem na stejnou instanci.
K implementaci této funkce pro record class typy kompilátor syntetizuje metodu klonování a kopírovací konstruktor. Metoda virtuálního klonování vrátí nový záznam inicializovaný kopírovacím konstruktorem. Když použijete výraz, kompilátor vytvoří kód, který volá metodu clone, a pak nastaví vlastnosti, with které jsou zadány ve with výrazu.
Pokud potřebujete jiné chování kopírování, můžete napsat vlastní konstruktor kopírování v record class . Pokud to chcete udělat, kompilátor ho syntetizovat nebude. Vytvořte private konstruktor, pokud je záznam sealed , jinak ho na proveďte protected . Kompilátor ne syntetizuje kopírovací konstruktor pro record struct typy. Můžete ho napsat, ale kompilátor nevygeneruje volání pro with výrazy. Místo toho kompilátor používá přiřazení.
Metodu klonování nelze přepsat a nelze vytvořit člen s názvem v žádném Clone typu záznamu. Skutečný název metody klonování je generován kompilátorem.
Integrované formátování pro zobrazení
Typy záznamů mají metodu vytvořenou kompilátorem, která zobrazuje názvy a hodnoty veřejných vlastností a ToString polí. Metoda ToString vrátí řetězec v následujícím formátu:
<record type name> { <property name> = <value>, <property name> = <value>, ...}
U odkazových typů se místo hodnoty vlastnosti zobrazuje název typu objektu, na který vlastnost odkazuje. V následujícím příkladu je pole odkazový typ, takže se zobrazí místo skutečných hodnot prvků System.String[] pole:
Person { FirstName = Nancy, LastName = Davolio, ChildNames = System.String[] }
Při implementaci této funkce kompilátor v typech record class syntetizuje virtuální metodu PrintMembers a ToString přepsání. V record struct typech je tento člen private .
Přepsání ToString vytvoří objekt s názvem typu StringBuilder následovaný počáteční závorkou. Zavolá PrintMembers metodu , která přidá názvy a hodnoty vlastností, a pak přidá uzavírací závorku. Následující příklad ukazuje kód podobný tomu, co syntetizované přepsání obsahuje:
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();
}
Můžete zadat vlastní implementaci PrintMembers nebo ToString přepsání. Příklady jsou uvedeny v PrintMembers oddílu formátování v odvozených záznamech dále v tomto článku. V C# 10 a novějších verzích může implementace obsahovat modifikátor , který kompilátoru brání v syntetizaci implementace pro ToString sealed všechny odvozené ToString záznamy. To znamená, že ToString výstup nebude obsahovat informace o typu modulu runtime. (Zobrazí se všichni členové a hodnoty, protože odvozené záznamy budou mít stále vygenerována metoda PrintMembers.)
Dědičnost
Tato část se vztahuje pouze na record class typy.
Záznam může dědit z jiného záznamu. Záznam ale nemůže dědit z třídy a třída nemůže dědit ze záznamu.
Poziční parametry v odvozených typech záznamů
Odvozený záznam deklaruje poziční parametry pro všechny parametry v primárním konstruktoru základního záznamu. Základní záznam deklaruje a inicializuje tyto vlastnosti. Odvozený záznam je neskrytý, ale pouze vytvoří a inicializuje vlastnosti pro parametry, které nejsou deklarovány v jeho základním záznamu.
Následující příklad znázorňuje dědičnost se syntaxí poziční vlastnosti:
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 }
}
Rovnost v hierarchiích dědičnosti
Tato část se vztahuje record class na typy, ale ne record struct na typy. Aby se dvě proměnné záznamů rovna, typ běhu musí být stejný. Typy obsahujících proměnných se mohou lišit. Zděděné porovnání rovnosti je znázorněno v následujícím příkladu kódu:
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
}
V tomto příkladu jsou všechny proměnné deklarovány jako , i když Person je instance odvozeným typem nebo Student Teacher . Instance mají stejné vlastnosti a stejné hodnoty vlastností. Vrátí se ale, i když obě jsou proměnné typu a vrací, i když jedna je student == teacher False Person student == student2 True Person proměnná a jedna Student proměnná. Test rovnosti závisí na typu modulu runtime skutečného objektu, nikoli na deklarovaného typu proměnné.
Při implementaci tohoto chování kompilátor syntetizuje vlastnost, která vrací objekt, který odpovídá EqualityContract Type typu záznamu. Umožňuje metodám rovnosti porovnat typ modulu runtime objektů při kontrole EqualityContract rovnosti. Pokud je základní typ záznamu object , je tato vlastnost virtual . Pokud je základním typem jiný typ záznamu, je tato vlastnost přepsáním. Pokud je typ záznamu sealed , je tato vlastnost sealed .
Při porovnávání dvou instancí odvozeného typu syntetizované metody rovnosti kontroluují rovnost všech vlastností základního a odvozeného typu. Syntetizovaná metoda používá metodu ze všech vlastností a polí deklarovaných v základním typu a GetHashCode GetHashCode odvozeném typu záznamu.
with Výrazy v odvozených záznamech
Výsledek výrazu with má stejný typ běhu jako operand výrazu. Zkopírují se všechny vlastnosti typu run-time, ale můžete nastavit pouze vlastnosti typu kompilace, jak ukazuje následující příklad:
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 formátování v odvozených záznamech
Syntetizovaná PrintMembers metoda odvozeného typu záznamu volá základní implementaci. Výsledkem je, že výstup obsahuje všechny veřejné vlastnosti a pole odvozeného i základního typu, jak je znázorněno ToString v následujícím příkladu:
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 }
}
Můžete poskytnout vlastní implementaci PrintMembers metody . Pokud to chcete udělat, použijte následující podpis:
- Pro
sealedzáznam, který je odvozen odobject(nedeklaruje základní záznam):private bool PrintMembers(StringBuilder builder); - Pro
sealedzáznam, který je odvozen od jiného záznamu:protected sealed override bool PrintMembers(StringBuilder builder); - Pro záznam, který není a
sealedje odvozen z objektu :protected virtual bool PrintMembers(StringBuilder builder); - Pro záznam, který není a
sealedje odvozen z jiného záznamu:protected override bool PrintMembers(StringBuilder builder);
Tady je příklad kódu, který nahrazuje syntetizované metody, jednu pro typ záznamu odvozenou z objektu a jednu pro typ záznamu odvozený z PrintMembers jiného záznamu:
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 }
}
Poznámka
V C# 10 a novějších verzích bude kompilátor syntetizovat, pokud základní záznam PrintMembers zapečetěl ToString metodu . Můžete také vytvořit vlastní implementaci PrintMembers .
Chování dekonstruktoru v odvozených záznamech
Metoda odvozeného záznamu vrátí hodnoty všech Deconstruct pozičních vlastností typu kompilace. Pokud je typ proměnné základní záznam, jsou vytvořeny pouze vlastnosti základního záznamu, pokud není objekt přetypován na odvozený typ. Následující příklad ukazuje volání dekonstruktoru u odvozeného záznamu.
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
}
Obecná omezení
Neexistuje žádné obecné omezení, které vyžaduje, aby typ byl záznam. Záznamy splňují omezení class nebo struct . Pokud chcete vytvořit omezení pro konkrétní hierarchii typů záznamů, dejte omezení základního záznamu stejně jako základní třídu. Další informace najdete v tématu Omezení parametrů typu.
specifikace jazyka C#
Další informace najdete v části Třídy specifikace jazyka C#.
Další informace o funkcích zavedených v jazyce C# 9 a novějších najdete v následujících poznámkách k návrhu funkcí: