Sdílet prostřednictvím


Struktury záznamů

Poznámka

Tento článek je specifikace funkce. Specifikace slouží jako návrhový dokument pro funkci. Zahrnuje navrhované změny specifikace spolu s informacemi potřebnými při návrhu a vývoji funkce. Tyto články se publikují, dokud nebudou navrhované změny specifikace finalizovány a začleněny do aktuální specifikace ECMA.

Mezi specifikací funkce a dokončenou implementací může docházet k nějakým nesrovnalostem. Tyto rozdíly jsou zachyceny v poznámkách ze schůzky návrhu jazyka (LDM) .

Další informace o procesu přijetí specifikací funkcí do jazyka C# najdete v článku o specifikacích .

Problém šampiona: https://github.com/dotnet/csharplang/issues/4334

Syntaxe struktury záznamu je následující:

record_struct_declaration
    : attributes? struct_modifier* 'partial'? 'record' 'struct' identifier type_parameter_list?
      parameter_list? struct_interfaces? type_parameter_constraints_clause* record_struct_body
    ;

record_struct_body
    : struct_body
    | ';'
    ;

Typy struktury záznamů jsou typy hodnot, jako jsou jiné typy struktur. Implicitně dědí z třídy System.ValueType. Modifikátory a členy struktury záznamu podléhají stejným omezením jako u struktur (přístupnost u typů, modifikátorů u členů, base(...) inicializátory konstruktoru instance, určité přiřazení pro this v konstruktoru, destruktory, ...). Struktury záznamů budou také dodržovat stejná pravidla jako struktury pro konstruktory instance bez parametrů a inicializátory polí, ale tento dokument předpokládá, že tato omezení pro struktury obecně zvedneme.

Viz §16.4.9 Viz konstruktory struktury bez parametrů specifikace.

Struktury záznamů nemohou používat modifikátor ref.

Nejméně jedna částečná deklarace typu částečné struktury záznamu může poskytnout parameter_list. parameter_list může být prázdný.

Parametry struktury záznamů nemůžou používat ref, out ani modifikátory this (ale jsou povoleny in a params).

Členové struktury záznamu

Kromě členů deklarovaných v těle struktury záznamu má typ struktury záznamu další syntetizované členy. Členové jsou syntetizováni, pokud není člen s "odpovídajícím" podpisem deklarován v těle struktury záznamu nebo pokud není zděděn přístupný konkrétní nevirtuální člen s "odpovídajícím" podpisem. Dva členové se považují za shodující se, pokud mají stejný podpis nebo by se ve scénáři dědičnosti považovali za skrývající. Viz Podpisy a přetížení §7.6. Je chybou, aby člen struktury záznamu měl název "Clone".

Jedná se o chybu, že pole instance struktury záznamu má nebezpečný typ.

Struktura záznamu není povolena k deklaraci destruktoru.

Syntetizované členy jsou následující:

Členové pro rovnost

Syntetizované členy rovnosti jsou obdobné jako ve třídě záznamů (Equals pro tento typ, Equals pro typ object, i == a != operátory pro tento typ).
kromě absence EqualityContract, kontrol na null nebo dědičnosti.

Struktura záznamu implementuje System.IEquatable<R> a zahrnuje generované silně typované přetížení Equals(R other), kde R je struktura záznamu. Metoda je public. Metodu lze deklarovat explicitně. Jedná se o chybu, pokud explicitní deklarace neodpovídá očekávanému podpisu nebo přístupnosti.

Pokud je Equals(R other) uživatelsky definovaná (ne syntetizována), ale GetHashCode není, vytvoří se upozornění.

public readonly bool Equals(R other);

Syntetizované Equals(R) vrátí true, pokud a jen když hodnota System.Collections.Generic.EqualityComparer<TN>.Default.Equals(fieldN, other.fieldN) pro každé datové pole fieldN ve záznamové struktuře, kde TN je typ pole, je true.

Struktura záznamu zahrnuje syntetizované == a operátory != ekvivalentní operátorům deklarovaným následujícím způsobem:

public static bool operator==(R r1, R r2)
    => r1.Equals(r2);
public static bool operator!=(R r1, R r2)
    => !(r1 == r2);

Metoda Equals volaná operátorem == je metoda Equals(R other) uvedená výše. Operátor != deleguje na operátor ==. Jedná se o chybu, pokud jsou operátory deklarovány explicitně.

Struktura záznamu zahrnuje syntetizované překrytí ekvivalentní metodě deklarované následujícím způsobem:

public override readonly bool Equals(object? obj);

Jedná se o chybu, pokud je přepsání deklarováno explicitně. Syntetizované přepsání vrátí other is R temp && Equals(temp), kde R je struktura záznamu.

Struktura záznamu zahrnuje syntetizované přepsání, které je ekvivalentní metodě deklarované následujícím způsobem:

public override readonly int GetHashCode();

Metodu lze deklarovat explicitně.

Upozornění je hlášeno, pokud jeden z Equals(R) a GetHashCode() je explicitně deklarován, ale druhá metoda není explicitní.

Syntetizované přepsání GetHashCode() vrátí výsledek int kombinování hodnot System.Collections.Generic.EqualityComparer<TN>.Default.GetHashCode(fieldN) pro každé pole instance fieldN, přičemž TN je typem fieldN.

Představte si například následující strukturu záznamu:

record struct R1(T1 P1, T2 P2);

Pro tuto strukturu záznamu by syntetizované členy pro kontrolu rovnosti vypadaly přibližně takto:

struct R1 : IEquatable<R1>
{
    public T1 P1 { get; set; }
    public T2 P2 { get; set; }
    public override bool Equals(object? obj) => obj is R1 temp && Equals(temp);
    public bool Equals(R1 other)
    {
        return
            EqualityComparer<T1>.Default.Equals(P1, other.P1) &&
            EqualityComparer<T2>.Default.Equals(P2, other.P2);
    }
    public static bool operator==(R1 r1, R1 r2)
        => r1.Equals(r2);
    public static bool operator!=(R1 r1, R1 r2)
        => !(r1 == r2);    
    public override int GetHashCode()
    {
        return Combine(
            EqualityComparer<T1>.Default.GetHashCode(P1),
            EqualityComparer<T2>.Default.GetHashCode(P2));
    }
}

Tisk členů: Metody PrintMembers a ToString

Struktura záznamu zahrnuje syntetizovanou metodu ekvivalentní metodě deklarované následujícím způsobem:

private bool PrintMembers(System.Text.StringBuilder builder);

Metoda provede následující:

  1. pro každý z tištěných členů záznamové struktury (nestatické veřejné pole a čitelné vlastnosti), připojí název člena, který následuje " = " a hodnotu člena oddělenou ", ".
  2. vrátí hodnotu true, pokud struktura záznamu má vytisknoutelné členy.

Pro člena, který má typ hodnoty, převedeme jeho hodnotu na řetězcovou reprezentaci pomocí nejúčinnější metody dostupné pro cílovou platformu. V současné době to znamená volání ToString před předáním do StringBuilder.Append.

Pokud tisknutelné členy záznamu neobsahují čitelný vlastnost s jiným nežreadonlyget příslušenstvím, syntetizovaný PrintMembers je readonly. Není nutné, aby pole záznamu byla na readonly, aby metodu readonlybylo možné použít pro metodu PrintMembers.

Metodu PrintMembers lze deklarovat explicitně. Jedná se o chybu, pokud explicitní deklarace neodpovídá očekávanému podpisu nebo přístupnosti.

Struktura záznamu zahrnuje syntetizovanou metodu ekvivalentní metodě deklarované následujícím způsobem:

public override string ToString();

Pokud je metoda PrintMembers struktury záznamu readonly, syntetizovaná ToString() metoda je readonly.

Metodu lze deklarovat explicitně. Jedná se o chybu, pokud explicitní deklarace neodpovídá očekávanému podpisu nebo přístupnosti.

Syntetizovaná metoda:

  1. vytvoří instanci StringBuilder,
  2. připojí název struktury záznamu k builderu následováním " { "
  3. vyvolá metodu PrintMembers struktury záznamu s tvůrcem jako argumentem, následovaná mezerou, pokud vrátí hodnotu true.
  4. připojí "}",
  5. vrátí obsah sestavovatele s builder.ToString().

Představte si například následující strukturu záznamu:

record struct R1(T1 P1, T2 P2);

Pro tuto strukturu záznamů by syntetizované tiskové členy vypadaly přibližně takto:

struct R1 : IEquatable<R1>
{
    public T1 P1 { get; set; }
    public T2 P2 { get; set; }

    private bool PrintMembers(StringBuilder builder)
    {
        builder.Append(nameof(P1));
        builder.Append(" = ");
        builder.Append(this.P1); // or builder.Append(this.P1.ToString()); if P1 has a value type
        builder.Append(", ");

        builder.Append(nameof(P2));
        builder.Append(" = ");
        builder.Append(this.P2); // or builder.Append(this.P2.ToString()); if P2 has 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();
    }
}

Členové poziční struktury záznamu

Kromě výše uvedených členů syntetizují struktury záznamů se seznamem parametrů ("poziční záznamy") další členy se stejnými podmínkami jako výše uvedené členy.

Primární konstruktor

Struktura záznamu má veřejný konstruktor, jehož podpis odpovídá parametrům hodnoty deklarace typu. Tomu se říká primární konstruktor pro typ. Je chybou mít primární konstruktor a konstruktor s totožným podpisem již ve struktuře. Pokud deklarace typu neobsahuje seznam parametrů, není generován žádný primární konstruktor.

record struct R1
{
    public R1() { } // ok
}

record struct R2()
{
    public R2() { } // error: 'R2' already defines constructor with same parameter types
}

Deklarace polí instance pro strukturu záznamu mohou obsahovat inicializátory proměnných. Pokud neexistuje žádný primární konstruktor, inicializátory instance se spustí jako součást konstruktoru bez parametrů. V opačném případě primární konstruktor spustí inicializátory instance, které se objevují v těle záznamově-strukturálního těla.

Pokud má struktura záznamu primární konstruktor, jakýkoli uživatelem definovaný konstruktor musí mít explicitní inicializátor this, který volá primární konstruktor nebo explicitně deklarovaný konstruktor.

Parametry primárního konstruktoru i členů struktury záznamu jsou v oboru v rámci inicializátorů polí instance nebo vlastností. Členové instance by na těchto místech způsobili chybu (podobně jako dnes členové instance v běžných inicializátorech konstruktoru jsou sice ve spojení, ale jejich použití způsobí chybu), avšak parametry primárního konstruktoru by byly v dosahu, použitelné a překryly by členy instance. Statické členy by také bylo možné použít.

Upozornění se vytvoří, pokud není přečtený parametr primárního konstruktoru.

Určitá pravidla přiřazení pro konstruktory instance struktury platí pro primární konstruktor struktury záznamů. Například následující chyba:

record struct Pos(int X) // definite assignment error in primary constructor
{
    private int x;
    public int X { get { return x; } set { x = value; } } = X;
}

Vlastnosti

Pro každý parametr záznamové struktury v deklaraci záznamové struktury existuje odpovídající člen veřejné vlastnosti, jehož název a typ jsou převzaty z deklarace parametru hodnoty.

Pro strukturu záznamu:

  • Veřejné automatické vlastnosti get a init se vytvoří, pokud má struktura záznamu modifikátor readonly; v opačném případě get a set. Oba druhy přístupových metod sad (set a init) se považují za "shodné". Uživatel tedy může deklarovat pouze inicializovatelnou vlastnost místo syntetizované měnitelné vlastnosti. Zděděná vlastnost abstract s odpovídajícím typem se přepíše. Pokud struktura záznamu obsahuje pole instance s očekávaným názvem a typem, není vytvořena žádná automatická vlastnost. Jedná se o chybu, pokud zděděná vlastnost nemá přístupory publicget a set/init. Jedná se o chybu, pokud je zděděná vlastnost nebo pole skryté.
    Auto-vlastnost je inicializována na hodnotu odpovídajícího parametru primárního konstruktoru. Atributy lze použít na syntetizované automatické vlastnosti a její zálohovací pole pomocí cílů property: nebo field:, které se vztahují na atributy syntakticky aplikované na odpovídající parametr struktury záznamu.

Dekonstrukce

Struktura pozičního záznamu s alespoň jedním parametrem syntetizuje veřejnou metodu instance void-returning, která se nazývá Deconstruct s deklarací výstupního parametru pro každý parametr deklarace primárního konstruktoru. Každý parametr Deconstruct metody má stejný typ jako odpovídající parametr deklarace primárního konstruktoru. Tělo metody přiřadí každý parametr metody Deconstruct k hodnotě z přístupu k členu instance se stejným názvem. Pokud členové instance přistupované v těle neobsahují vlastnost s jiným nežreadonlyget příslušenstvím, syntetizovaná metoda Deconstruct je readonly. Metodu lze deklarovat explicitně. Jedná se o chybu, pokud explicitní deklarace neodpovídá očekávanému podpisu nebo přístupnosti nebo je statická.

Povolit výraz with pro struktury

Nyní je pro příjemce ve výrazu with platné mít typ struktury.

Na pravé straně výrazu with je member_initializer_list s posloupností přiřazení k identifikátoru, což musí být přístupné pole instance nebo vlastnost typu příjemce.

Pro příjemce s typem struktury se příjemce nejprve zkopíruje, pak se každý member_initializer zpracuje stejným způsobem jako přiřazení k poli nebo vlastnosti přístupu k výsledku převodu. Přiřazení se zpracovávají v lexikálním pořadí.

Vylepšení záznamů

Povolit record class

Stávající syntaxe pro typy záznamů umožňuje record class se stejným významem jako record:

record_declaration
    : attributes? class_modifier* 'partial'? 'record' 'class'? identifier type_parameter_list?
      parameter_list? record_base? type_parameter_constraints_clause* record_body
    ;

Povolit poziční členy definované uživatelem, aby byly polem

Podívejte se na https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-10-05.md#changing-the-member-type-of-a-primary-constructor-parameter

Není vytvořena žádná automatická vlastnost, pokud záznam obsahuje nebo dědí pole instance s očekávaným názvem a typem.

Povolit konstruktory bez parametrů a inicializátory členů ve strukturách

Viz konstruktory struktury bez parametrů specifikace.

Otevřené otázky

  • jak rozpoznat struktury záznamů v metadatech? (Nemáme nevyřčenou metodu klonování, kterou by se využilo...)

Odpovězeno

  • potvrďte, že chceme zachovat návrh PrintMembers (samostatná metoda vracející bool) (odpověď: ano).
  • Potvrďte, že nepovolíme record ref struct (problém s poli IEquatable<RefStruct> a ref poli) (odpověď: ano).
  • potvrdit implementaci členů pro rovnost. Alternativou je, že syntetizované bool Equals(R other), bool Equals(object? other) a operátory pouze všechny delegují na ValueType.Equals. (odpověď: ano)
  • Potvrďte, že chceme povolit inicializátory polí, pokud existuje primární konstruktor. Chceme také povolit konstruktory struktury bez parametrů, když už na tom jsme (problém s aktivátorem byl zřejmě opraven)? (odpověď: Ano, aktualizovaná specifikace by měla být zkontrolována v LDM)
  • kolik chceme říct o Combine metodě? (odpověď: co nejmenší)
  • měli bychom zakázat uživatelsky definovaný konstruktor s podpisem kopírovacího konstruktoru? (odpověď: ne, neexistuje žádná koncepce konstruktoru kopírování ve specifikaci struktury záznamu)
  • potvrďte, že chceme zakázat členy s jménem "Clone". (odpověď: správná)
  • Pečlivě zkontrolujte, že syntetizovaná logika Equals je funkčně ekvivalentní implementaci za běhu programu (např. float.NaN) (odpověď: potvrzena v LDM)
  • mohou být atributy cílení na pole nebo vlastnosti umístěny v seznamu pozičních parametrů? (odpověď: ano, stejná jako u třídy záznamů)
  • with generika? (odpověď: není zahrnuto v rozsahu pro C# 10)
  • Měl by GetHashCode zahrnovat hodnotu hash samotného typu, aby se získaly různé hodnoty mezi record struct S1; a record struct S2;? (odpověď: ne)