Záznamy (referenční dokumentace jazyka C#)

Počínaje jazykem C# 9 můžete pomocí klíčového record slova definovat typ odkazu , který poskytuje předdefinované funkce pro zapouzdření dat. C# 10 umožňuje record class syntaxi jako synonyma objasnit typ odkazu a record struct definovat typ hodnoty s podobnými funkcemi. Typy záznamů s neměnnými vlastnostmi můžete vytvářet pomocí pozičních parametrů nebo standardní syntaxe vlastností.

Následující dva příklady ukazují record (nebo record class) odkazové typy:

public record Person(string FirstName, string LastName);
public record Person
{
    public string FirstName { get; init; } = default!;
    public string LastName { get; init; } = default!;
};

Následující dva příklady ukazují record struct typy hodnot:

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

Můžete také vytvořit záznamy s proměnlivými vlastnostmi a poli:

public record Person
{
    public string FirstName { get; set; } = default!;
    public string LastName { get; set; } = default!;
};

Struktury záznamů můžou být také proměnlivé, a to jak struktury pozičních záznamů, tak 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; }
}

I když záznamy můžou být proměnlivé, jsou primárně určené pro podporu neměnných datových modelů. Typ záznamu nabízí následující funkce:

V předchozích příkladech jsou uvedeny některé rozdíly mezi záznamy, které jsou odkazovými typy a záznamy, které jsou typy hodnot:

  • A record nebo record class deklaruje typ odkazu. Klíčové class slovo je volitelné, ale může přidat srozumitelnost pro čtenáře. A record struct deklaruje typ hodnoty.
  • Poziční vlastnosti jsou neměnné v objektu record class a .readonly record struct Jsou proměnlivé ve směšnérecord struct.

Zbývající část tohoto článku popisuje oba record class typy i record struct typy. Rozdíly jsou podrobně popsané v jednotlivých částech. Měli byste se rozhodnout mezi a record class podobným record struct rozhodnutím mezi a struct.class Záznam termínu slouží 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 struktury nebo typy tříd, v uvedeném pořadí. Typ record byl zaveden v jazyce C# 9; record struct typy byly zavedeny v jazyce C# 10.

Poziční syntaxe pro definici vlastnosti

Pomocí pozičních parametrů můžete deklarovat vlastnosti záznamu a inicializovat hodnoty 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 }
}

Při použití poziční syntaxe pro definici vlastnosti kompilátor vytvoří:

  • Veřejná automaticky implementovaná vlastnost pro každý poziční parametr zadaný v deklaraci záznamu.
    • Typy record a readonly record struct typy: Vlastnost pouze init .
    • Pro record struct typy: Vlastnost pro čtení i zápis.
  • Primární konstruktor, jehož parametry odpovídají pozičním parametrům deklarace záznamu.
  • Pro typy struktur záznamů konstruktor bez parametrů, který nastaví každé pole na výchozí hodnotu.
  • Deconstruct Metoda s parametrem out pro každý poziční parametr zadaný v deklaraci záznamu. Metoda dekonstruuje vlastnosti definované pomocí poziční syntaxe; ignoruje vlastnosti definované pomocí standardní syntaxe vlastností.

Do některého z těchto prvků můžete přidat atributy, které kompilátor vytvoří z definice záznamu. Cíl můžete přidat do libovolného atributu, který použijete pro vlastnosti pozičního záznamu. Následující příklad použije pro System.Text.Json.Serialization.JsonPropertyNameAttribute každou vlastnost záznamu Person . property: Cíl označuje, že atribut se použije na vlastnost vygenerovanou kompilátorem. Další hodnoty se field: týkají použití atributu u pole a param: 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 komentáře dokumentace XML pro záznam. Značku můžete přidat a přidat <param> dokumentaci k parametrům primárního konstruktoru.

Pokud vygenerovaná definice automaticky implementované vlastnosti není to, co chcete, můžete definovat vlastní vlastnost stejného názvu. Můžete například chtít změnit přístupnost nebo ztlumitelnost nebo poskytnout implementaci buď pro objekt get nebo set příslušenství. Pokud deklarujete vlastnost ve zdroji, musíte ji inicializovat z pozičního parametru záznamu. Pokud je vlastnost automaticky implementovaná, musíte vlastnost inicializovat. Pokud do zdroje přidáte záložní pole, musíte inicializovat záložní pole. Vygenerovaný dekonstruktor použije definici vaší vlastnosti. Například následující příklad deklaruje FirstName a LastName vlastnosti 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 struktur záznamů.

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. Záznam můžete deklarovat bez jakýchkoli 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í standardní syntaxe vlastnosti, ale vynecháte modifikátor přístupu, vlastnosti jsou implicitně private.

Neměnnost

Positional record and a positional readonly record struct deklarovat init-only properties. Struktura pozičního záznamu deklaruje vlastnosti pro čtení a zápis. Můžete přepsat některou z těchto výchozích hodnot, jak je znázorněno v předchozí části.

Neměnnost může být užitečná, když potřebujete typ orientovaný na data, aby byl bezpečný pro vlákno, nebo v závislosti na kódu hash, který zůstává stejný v tabulce hash. Neměnnost ale není vhodná pro všechny scénáře dat. Entity Framework Core například nepodporuje aktualizaci pomocí neměnných typů entit.

Vlastnosti pouze inicializační, ať už vytvořené z pozičních parametrů (record classa readonly record struct) nebo určením init přístupových objektů, mají neměnnost. Po inicializaci nemůžete změnit hodnotu vlastností typu hodnoty ani odkaz na vlastnosti typu odkazu. Data, na která vlastnost typu odkaz odkazuje, se však dají změnit. Následující příklad ukazuje, že obsah neměnné vlastnosti typu odkazu (v tomto případě pole) 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 jedinečné pro typy záznamů jsou implementovány syntetizovanými metodami kompilátoru a žádné z těchto metod ohrozit neměnnost úpravou stavu objektu. Pokud není uvedeno, syntetizované metody jsou generovány pro record, record structa readonly record struct deklarace.

Rovnost hodnot

Pokud nepřepíšete nebo nenahrazujete metody rovnosti, typ, který deklarujete, řídí způsob definování rovnosti:

  • U class typů jsou dva objekty stejné, pokud odkazují na stejný objekt v paměti.
  • U struct typů jsou dva objekty stejné, pokud jsou stejného typu a ukládají stejné hodnoty.
  • U record typů, včetně record struct a readonly 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 u .struct Rozdíl je v tom, že implementace structje v ValueType.Equals(Object) a spoléhá na reflexi. U záznamů je implementace syntetizována kompilátorem a používá deklarované datové členy.

U některých datových modelů je vyžadována rovnost odkazů. Například Entity Framework Core závisí na rovnosti odkazů, aby se zajistilo, že pro to, co je koncepčně jedna entita, používá pouze jednu instanci typu entity. 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 rovnost hodnot 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ěkolik metod, mezi které patří:

  • Přepsání .Object.Equals(Object) Jedná se o chybu, pokud je přepsání deklarováno explicitně.

    Tato metoda se používá jako základ pro statickou metodu Object.Equals(Object, Object) , pokud oba parametry nejsou null.

  • A virtual, nebo sealed, Equals(R? other) kde R je typ záznamu. Tato metoda implementuje IEquatable<T>. Tuto metodu lze deklarovat explicitně.

  • Je-li typ záznamu odvozen od základního typu Basezáznamu , Equals(Base? other). Jedná se o chybu, pokud je přepsání deklarováno explicitně. Pokud zadáte vlastní implementaci Equals(R? other), poskytněte také implementaci GetHashCode .

  • Přepsání .Object.GetHashCode() Tuto metodu lze deklarovat explicitně.

  • Přepsání operátorů == a !=. Jedná se o chybu, pokud jsou operátory deklarovány explicitně.

  • Je-li typ záznamu odvozen od základního typu záznamu, protected override Type EqualityContract { get; };. Tuto vlastnost lze deklarovat explicitně. Další informace naleznete v tématu Rovnost v hierarchiích dědičnosti.

Pokud má typ záznamu metodu, která odpovídá podpisu syntetizované metody, je možné explicitně deklarovat, kompilátor syntetizuje danou metodu.

Nedestruktivní mutaci

Pokud potřebujete zkopírovat instanci s některými úpravami, můžete pomocí výrazu with dosáhnout nedestruktivní mutaci. Výraz with vytvoří novou instanci záznamu, která je kopií existující instance záznamu se zadanými vlastnostmi a poli změněnými. Syntaxe inicializátoru objektů slouží k určení hodnot, které se mají změnit, 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
}

Výraz with může nastavit poziční vlastnosti nebo vlastnosti vytvořené pomocí standardní syntaxe vlastností. Nepoziční vlastnosti musí mít ve výrazu with změněný objekt init nebo set přístupový objekt.

Výsledkem výrazu with je mělká kopie, což znamená, že pro referenční vlastnost se zkopíruje pouze odkaz na instanci. Původní záznam i kopie končí odkazem na stejnou instanci.

Chcete-li implementovat tuto funkci pro record class typy, kompilátor syntetizuje klonovací metodu a konstruktor kopírování. Metoda virtuálního klonování vrátí nový záznam inicializovaný konstruktorem kopírování. Při použití with výrazu kompilátor vytvoří kód, který volá metodu klonu a potom nastaví vlastnosti zadané ve výrazu with .

Pokud potřebujete jiné chování kopírování, můžete napsat vlastní konstruktor kopírování do record class. Pokud to uděláte, kompilátor ho syntetizuje. Nastavte konstruktor private , pokud je sealedzáznam , jinak ho protectedudělejte . Kompilátor syntetizuje konstruktor kopírování pro record struct typy. Můžete ho napsat, ale kompilátor ho negeneruje pro with výrazy. Hodnoty se record struct zkopírují při přiřazení.

Klonovací metodu nelze přepsat a nemůžete vytvořit člena pojmenovaného Clone v žádném typu záznamu. Skutečný název klonovací metody je vygenerován kompilátorem.

Integrované formátování pro zobrazení

Typy záznamů mají metodu vygenerovanou ToString kompilátorem, která zobrazuje názvy a hodnoty veřejných vlastností a polí. Metoda ToString vrátí řetězec následujícího formátu:

<název typu záznamu { <název>> vlastnosti = <hodnota>, <název> vlastnosti = <hodnota>, ...}

Řetězec vytištěný pro <value> je řetězec vrácený ToString() typem vlastnosti. V následujícím příkladu ChildNames je hodnota System.Array, kde ToString vrátí System.String[]:

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

Pokud chcete tuto funkci implementovat, record class kompilátor syntetizuje virtuální PrintMembers metodu a přepsání ToString . V record struct typech je privatetento člen . Přepsání ToStringStringBuilder vytvoří objekt s názvem typu následovaným levou závorkou. Volá PrintMembers přidání názvů vlastností a hodnot a pak přidá pravou 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 formátování v oddíluPrintMembers odvozených záznamů dále v tomto článku. V jazyce C# 10 a novější může ToString implementace obsahovat sealed modifikátor, který brání kompilátoru v syntetizaci ToString implementace pro všechny odvozené záznamy. Můžete to udělat tak, že vytvoříte konzistentní řetězcovou reprezentaci v celé hierarchii record typů. (Odvozené záznamy budou mít stále vygenerovanou metodu PrintMembers pro všechny odvozené vlastnosti.)

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 však 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 primárního konstruktoru základního záznamu. Základní záznam deklaruje a inicializuje tyto vlastnosti. Odvozený záznam je neskryje, ale vytvoří a inicializuje pouze vlastnosti parametrů, které nejsou deklarovány v 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 týká record class typů, ale ne record struct typů. Aby se dvě proměnné záznamu srovnaly, musí být typ běhu roven. Typy obsahujících proměnných se můžou lišit. Porovnání zdědě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 příkladu jsou všechny proměnné deklarovány jako Person, i když je instance odvozeným typem buď Student nebo Teacher. Instance mají stejné vlastnosti a stejné hodnoty vlastností. I student == teacher když jsou Personobě proměnné typu - a student == student2 vrátíTrue, i když jedna je Person proměnná a jedna je proměnnáStudent.False Test rovnosti závisí na typu modulu runtime skutečného objektu, nikoli deklarovaného typu proměnné.

Chcete-li implementovat toto chování, kompilátor syntetizuje EqualityContract vlastnost, která vrací Type objekt, který odpovídá typu záznamu. Umožňuje EqualityContract metodám rovnosti porovnat typ modulu runtime objektů při kontrole rovnosti. Pokud je objectzákladním typem záznamu , je tato vlastnost virtual. Pokud je základním typem jiný typ záznamu, je tato vlastnost přepsána. Pokud je sealedtyp záznamu , tato vlastnost je efektivní sealed , protože typ je sealed.

Při porovnávání dvou instancí odvozeného typu syntetizované metody rovnosti zkontrolují všechny vlastnosti základního a odvozeného typu pro rovnost. Syntetizovaná GetHashCode metoda používá metodu GetHashCode ze všech vlastností a polí deklarovaných v základním typu a odvozeného 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. Všechny vlastnosti typu spuštění se zkopírují, 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 do výstupu ToString jsou zahrnuty všechny veřejné vlastnosti a pole odvozených i základních typů, jak je znázorněno 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 zadat vlastní implementaci PrintMembers metody. Pokud to uděláte, použijte následující podpis:

  • sealed Pro záznam odvozený z object (deklaruje základní záznam): private bool PrintMembers(StringBuilder builder);
  • sealed Pro záznam, který je odvozen z jiného záznamu (všimněte si, že uzavřený typ je sealed, takže metoda je efektivní sealed): protected override bool PrintMembers(StringBuilder builder);
  • Pro záznam, který není sealed a je odvozen z objektu: protected virtual bool PrintMembers(StringBuilder builder);
  • Pro záznam, který není sealed a odvozený z jiného záznamu: protected override bool PrintMembers(StringBuilder builder);

Tady je příklad kódu, který nahrazuje syntetizované PrintMembers metody, jeden pro typ záznamu, který je odvozen z objektu, a jeden pro typ záznamu, který je odvozen z 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 jazyce C# 10 a novější kompilátor syntetizuje PrintMembers v odvozených záznamech i v případě, že základní záznam zapečetil metodu ToString . Můžete také vytvořit vlastní implementaci PrintMembers.

Chování dekonstruktoru v odvozených záznamech

Metoda Deconstruct odvozeného záznamu vrátí hodnoty všech pozičních vlastností typu kompilace. Pokud je typ proměnné základním záznamem, jsou vytvořeny pouze vlastnosti základního záznamu, pokud objekt není přetypován na odvozený typ. Následující příklad ukazuje volání dekonstruktoru na odvozený záznam.

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 byl typ záznamem. Záznamy splňují buď omezení class , nebo struct omezení. Pokud chcete vytvořit omezení pro konkrétní hierarchii typů záznamů, umístěte omezení na základní záznam stejně jako základní třídu. Další informace najdete v tématu Omezení parametrů typu.

specifikace jazyka C#

Další informace najdete v části Třídyspecifikace 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í:

Viz také