Typy záznamů jazyka C#

Návod

Začínáte s vývojem softwaru? Začněte nejprve kurzy Začínáme . Narazíte na záznamy, když potřebujete stručné datové typy s integrovanou rovností.

Máte zkušenosti v jiném jazyce? Záznamy v jazyce C# jsou podobné datovým třídám v Kotlinu nebo případovým třídám v jazyce Scala. Jsou to typy optimalizované pro ukládání dat s rovností generovanou ToStringkompilátorem a sémantikou kopírování. Projděte sekce record class vs record struct a with výrazy pro vzory specifické pro jazyk C#.

Klíčové record slovo je modifikátor, který použijete buď na class nebo na struct. Dává kompilátoru pokyn, aby vygeneroval rovnost hodnot, formátovanou ToStringa nedestruktivní mutování prostřednictvím with výrazů. Základní typ, třída nebo struktura, stále určuje, zda instance používají sémantiku odkazu nebo hodnoty. record Modifikátor přidává nad tyto sémantiky chování přívětivé k datům. Záznamy použijte, když je primární rolí typu ukládání dat a dvě instance s těmito stejnými hodnotami by měly být považovány za ekvivalentní.

Kdy použít záznamy

Záznam použijte, pokud jsou splněny všechny následující podmínky:

  • Primární role typu je ukládání dat.
  • Dvě instance se stejnými hodnotami by měly být stejné.
  • Chcete neměnnost (zejména pro record class typy).
  • Chcete čitelný ToString bez ručního zápisu.

Při výběru mezi record class a record struct:

  • Použijte record class, když potřebujete dědičnost nebo když je typ dostatečně velký, aby kopírování při každém přiřazení bylo nákladné.
  • Používá se record struct pro malé, samostatně obsažené hodnoty, ve kterých je přínosná sémantika kopírování a přidělování zásobníku.

Vyhněte se záznamům pro typy entit v Entity Framework Core, protože závisí na rovnosti referencí pro sledování entit. Širší porovnání možností typů najdete v tématu Volba typu.

Deklarace záznamu

Můžete použít record buď pro třídu, nebo strukturu. Nejjednodušší formulář používá poziční parametry , které definují konstruktor i vlastnosti v jednom řádku:

public record Person(string FirstName, string LastName);

Stejná poziční syntaxe funguje pro record struct typy:

public record struct Coordinate(double Latitude, double Longitude);

public readonly record struct Temperature(double Celsius)
{
    public double Fahrenheit => Celsius * 9.0 / 5.0 + 32.0;
}

Psaní record samo o sobě je zkratka pro record class, což je referenční typ. Zápis record struct vytvoří typ hodnoty. Kompilátor vygeneruje vlastnosti z pozičních parametrů v obou případech, ale výchozí hodnoty se liší:

  • record class: Vlastnosti jsou init-only (neměnné po konstrukci).
  • record struct: Vlastnosti jsou ve výchozím nastavení pro čtení i zápis. Přidejte readonly (readonly record struct) a nastavte je pouze na init.

Pokud potřebujete větší kontrolu, můžete také psát záznamy se standardní syntaxí vlastností. Pokud chcete například vytvořit vlastnost pro čtení/zápis místo init-only:

public record Product
{
    public required string Name { get; init; }
    public decimal Price { get; set; }
}

record class Vs. record struct

Vzhledem k tomu, že modifikátor zachovává sémantiku základního typu, record a record class se při přiřazování nebo porovnávání odkazů chovají odlišně. Přiřazení record class okopíruje odkaz. Obě proměnné odkazují na stejný objekt. Přiřazení record struct vytvoří kopii dat, takže změny v jedné proměnné neovlivní druhou.

// Record class — assignment copies the reference
var p1 = new Person("Grace", "Hopper");
var p2 = p1; // p1 and p2 point to the same object:
Console.WriteLine(ReferenceEquals(p1, p2)); // True

// Record struct — assignment copies the data
var c1 = new Coordinate(47.6062, -122.3321);
var c2 = c1;
c2.Longitude = 0.0; // mutating c2 doesn't affect c1
Console.WriteLine(c1.Longitude); // -122.3321
Console.WriteLine(c2.Longitude); // 0

Struktury záznamů také poskytují rovnost hodnot generovaných kompilátorem:

var home = new Coordinate(47.6062, -122.3321);
var copy = home;

Console.WriteLine(home);           // Coordinate { Latitude = 47.6062, Longitude = -122.3321 }
Console.WriteLine(home == copy);   // True — value equality

Zvolte record class , kdy potřebujete dědičnost nebo kdy jsou instance dostatečně velké, aby kopírování bylo nákladné. Zvolte record struct pro malá, samostatná data, kde je vhodná sémantika kopírování hodnotového typu. Další informace o sémantice typu hodnoty najdete v tématu Struktury.

Rovnost hodnot

record Modifikátor poskytuje jak třídám, tak strukturám kompilátorem generovanou rovnost vlastnost-po-vlastnosti. Tady je postup, jak rovnost funguje ve všech čtyřech typech:

  • Plain class: Ve výchozím nastavení používá rovnost odkazů . Operátor == zkontroluje, jestli dvě proměnné odkazují na stejný objekt, ne na to, jestli se data shodují.
  • Prosté struktury: Podporuje rovnost hodnot prostřednictvím ValueType.Equals, ale výchozí implementace používá reflexi, což je pomalejší a negeneruje ==/!= operátory.
  • record class: Kompilátor generuje Equals metody a GetHashCode==/ operátory !=, které porovnávají každou hodnotu vlastnosti. Dva odlišné objekty se stejnými daty jsou stejné.
  • record struct: Stejná rovnost struktur generovaná kompilátorem jako record class, ale bez použití reflexe, což z ní činí rychlejší variantu než u rovnosti prostých struktur.

Následující příklad ukazuje rovnost tříd záznamů:

// Person is a record type with three properties: FirstName, LastName, and PhoneNumbers.
var phones = new string[] { "555-1234" };
var person1 = new Person("Grace", "Hopper", phones);
var person2 = new Person("Grace", "Hopper", phones);

Console.WriteLine(person1 == person2);              // True
Console.WriteLine(ReferenceEquals(person1, person2)); // False

person1.PhoneNumbers[0] = "555-9999";
Console.WriteLine(person2.PhoneNumbers[0]); // 555-9999 — same array

Tyto dvě Person instance jsou různé objekty, ale jsou stejné, protože všechny jejich hodnoty vlastností se shodují. Vlastnosti pole se porovnávají podle odkazu, ne podle obsahu. Modifikace sdíleného pole je patrná z obou záznamů, protože samotné pole není kopírováno.

Nedestruktivní mutace a with výrazy

Záznamy jsou často neměnné, takže po vytvoření nemůžete změnit vlastnost. Výraz with vytvoří kopii s jednou nebo více změněnými vlastnostmi a původní záznam zůstane beze změny. Tento přístup funguje u obou record class typů record struct :

var original = new Person("Grace", "Hopper");
var modified = original with { FirstName = "Margaret" };

Console.WriteLine(original); // Person { FirstName = Grace, LastName = Hopper }
Console.WriteLine(modified); // Person { FirstName = Margaret, LastName = Hopper }
Console.WriteLine(original == modified); // False

var copy = original with { };
Console.WriteLine(original == copy); // True

Stejná syntaxe funguje pro record struct typy:

var shifted = home with { Longitude = -122.0 };
Console.WriteLine(shifted);        // Coordinate { Latitude = 47.6062, Longitude = -122 }
Console.WriteLine(home == shifted); // False

Výraz with zkopíruje existující instanci a pak použije změny zadané vlastnosti.

Poziční záznamy a dekonstrukce

Poziční záznamy generují metodu Deconstruct , kterou použijete k extrakci hodnot vlastností do jednotlivých proměnných:

var (first, last) = person;
Console.WriteLine($"{first} {last}");
// Grace Hopper

Dekonstrukce funguje s oběma typy: record class a record struct. Můžete ho použít v přiřazeních, foreach smyčkách a porovnávání vzorů.

Dědičnost záznamů

A record class může dědit z jiného record class. Záznam nemůže dědit z běžné třídy a třída nemůže dědit ze záznamu:

public record Student(string FirstName, string LastName, int GradeLevel)
    : Person(FirstName, LastName);

Kontroly rovnosti hodnot zahrnují typ běhu, takže hodnota Person a shodné StudentFirstName a LastName nejsou považovány za stejné. Struktury záznamů nepodporují dědičnost, protože struktury nemohou dědit z jiných typů.

Viz také