Záznamy
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 schůze 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/39
Tento návrh sleduje specifikaci funkce záznamů C# 9, jak bylo dohodnuto týmem pro návrh jazyka C#.
Syntaxe záznamu je následující:
record_declaration
: attributes? class_modifier* 'partial'? 'record' identifier type_parameter_list?
parameter_list? record_base? type_parameter_constraints_clause* record_body
;
record_base
: ':' class_type argument_list?
| ':' interface_type_list
| ':' class_type argument_list? ',' interface_type_list
;
record_body
: '{' class_member_declaration* '}' ';'?
| ';'
;
Typy záznamů jsou odkazové typy podobné deklaraci třídy. Je chybou, pokud záznam poskytuje record_base
argument_list
, jestliže record_declaration
neobsahuje parameter_list
.
Nejvýše jedna částečná deklarace typu částečného záznamu může poskytnout parameter_list
.
Parametry záznamu nemůžou používat ref
, out
ani modifikátory this
(ale jsou povolené in
a params
).
Dědičnost
Záznamy nemohou dědit z tříd, pokud třída není object
a třídy nemohou dědit ze záznamů. Záznamy mohou dědit od jiných záznamů.
Členové typu záznamu
Kromě členů deklarovaných v těle záznamu má typ záznamu další syntetizované členy. Členové jsou syntetizováni, pokud není v těle záznamu deklarován člen s odpovídajícím podpisem nebo není zděděn přístupný konkrétní nevirtuální člen s odpovídajícím podpisem. Odpovídající člen zabraňuje kompilátoru vygenerovat tento člen, ne žádné jiné syntetizované členy. Dva členové se považují za shodní, pokud mají stejný podpis nebo by byli považováni za 'skrývající se' ve scénáři dědičnosti. Jedná se o chybu, když se člen záznamu jmenuje "Clone". Jedná se o chybu, že pole instance záznamu má typ ukazatele nejvyšší úrovně. Je povolený vnořený typ ukazatele, například pole ukazatelů.
Syntetizované členy jsou následující:
Členové pro rovnost
Pokud je záznam odvozen z object
, typ záznamu obsahuje syntetizovanou vlastnost pouze pro čtení, která je ekvivalentní vlastnosti deklarované následujícím způsobem:
Type EqualityContract { get; }
Vlastnost je private
, pokud je typ záznamu sealed
. V opačném případě je vlastnost virtual
a protected
.
Vlastnost lze deklarovat explicitně. Jedná se o chybu, pokud explicitní deklarace neodpovídá očekávanému podpisu nebo přístupnosti, nebo pokud neumožňuje přepsání v odvozeném typu a typ záznamu není sealed
.
Pokud je typ záznamu odvozen od základního typu záznamu Base
, typ záznamu obsahuje syntetizovaný jen pro čtení ekvivalentní vlastnosti deklarované následujícím způsobem:
protected override Type EqualityContract { get; }
Vlastnost lze deklarovat explicitně. Jedná se o chybu, pokud explicitní deklarace neodpovídá očekávanému podpisu nebo přístupnosti, nebo pokud neumožňuje přepsání v odvozeném typu a typ záznamu není sealed
. Jedná se o chybu, pokud syntetizovaná nebo explicitně deklarovaná vlastnost nepřepíše vlastnost se stejnou signaturou v typu záznamu Base
(například pokud je vlastnost v Base
chybějící, zapečetěná, není virtuální atd.).
Syntetizovaná vlastnost vrátí typeof(R)
, kde R
je typ záznamu.
Typ záznamu implementuje System.IEquatable<R>
a zahrnuje syntetizované silně typované přetížení Equals(R? other)
, kde R
je typ záznamu.
Metoda je public
a metoda je virtual
, pokud typ záznamu není sealed
.
Metodu lze deklarovat explicitně. Jedná se o chybu, pokud explicitní deklarace neodpovídá očekávanému podpisu nebo přístupnosti, nebo pokud explicitní deklarace neumožňuje, aby byla přepsána v odvozeném typu, a zároveň typ záznamu není sealed
.
Pokud je Equals(R? other)
uživatelsky definovaná (ne syntetizována), ale GetHashCode
není, vytvoří se upozornění.
public virtual bool Equals(R? other);
Syntetizované Equals(R?)
vrátí true
, a jen tehdy, pokud jsou všechny následující true
:
-
other
nenínull
, a - Pro každé pole instance
fieldN
v typu záznamu, který není zděděný, hodnotaSystem.Collections.Generic.EqualityComparer<TN>.Default.Equals(fieldN, other.fieldN)
, kdeTN
je typ pole a - Pokud je základní typ záznamu, hodnota
base.Equals(other)
(nevirtuální volánípublic virtual bool Equals(Base? other)
); jinak hodnotaEqualityContract == other.EqualityContract
.
Typ záznamu zahrnuje syntetizované ==
a operátory !=
ekvivalentní operátorům deklarovaným následujícím způsobem:
public static bool operator==(R? left, R? right)
=> (object)left == right || (left?.Equals(right) ?? false);
public static bool operator!=(R? left, R? right)
=> !(left == right);
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ě.
Pokud je typ záznamu odvozen od základního typu záznamu Base
, typ záznamu obsahuje syntetizované přepsání ekvivalentní metodě deklarované takto:
public sealed override bool Equals(Base? other);
Jedná se o chybu, pokud je přepsání deklarováno explicitně. Jedná se o chybu, pokud metoda nepřepíše metodu se stejným podpisem v typu záznamu Base
(například pokud je metoda chybějící, nebo je zapečetěná, nebo není virtuální, nebo chybí v Base
atd.).
Syntetizované přepsání vrátí Equals((object?)other)
.
Typ záznamu obsahuje syntetizovaný přepis ekvivalentní metodě deklarované následujícím způsobem:
public override bool Equals(object? obj);
Jedná se o chybu, pokud je přepsání deklarováno explicitně. Je chybou, pokud metoda nepřepíše object.Equals(object? obj)
, například kvůli překrývání v mezilehlých základových typech atd.
Syntetizované přepsání vrátí Equals(other as R)
, kde R
je typ záznamu.
Typ záznamu obsahuje syntetizovaný přepis ekvivalentní metodě deklarované následujícím způsobem:
public override int GetHashCode();
Metodu lze deklarovat explicitně.
Jedná se o chybu, pokud explicitní deklarace neumožňuje její přepsání v odvozeném typu a typ záznamu není sealed
. Jedná se o chybu, pokud syntetizovaná nebo explicitně deklarovaná metoda nepřepíše object.GetHashCode()
(například kvůli stínování v mezilehlých základních typech atd.).
Upozornění je hlášeno, pokud jeden z Equals(R?)
a GetHashCode()
je explicitně deklarován, ale druhá metoda není explicitní.
Syntetické přepsání GetHashCode()
vrátí výsledek int
ze sloučení následujících hodnot:
- Pro každé pole instance
fieldN
v typu záznamu, který není zděděný, hodnotaSystem.Collections.Generic.EqualityComparer<TN>.Default.GetHashCode(fieldN)
, kdeTN
je typ pole a - Pokud existuje základní typ záznamu, hodnota
base.GetHashCode()
; jinak hodnotaSystem.Collections.Generic.EqualityComparer<System.Type>.Default.GetHashCode(EqualityContract)
.
Představte si například následující typy záznamů:
record R1(T1 P1);
record R2(T1 P1, T2 P2) : R1(P1);
record R3(T1 P1, T2 P2, T3 P3) : R2(P1, P2);
U těchto typů záznamů by syntetizované členy rovnosti vypadaly přibližně takto:
class R1 : IEquatable<R1>
{
public T1 P1 { get; init; }
protected virtual Type EqualityContract => typeof(R1);
public override bool Equals(object? obj) => Equals(obj as R1);
public virtual bool Equals(R1? other)
{
return !(other is null) &&
EqualityContract == other.EqualityContract &&
EqualityComparer<T1>.Default.Equals(P1, other.P1);
}
public static bool operator==(R1? left, R1? right)
=> (object)left == right || (left?.Equals(right) ?? false);
public static bool operator!=(R1? left, R1? right)
=> !(left == right);
public override int GetHashCode()
{
return HashCode.Combine(EqualityComparer<Type>.Default.GetHashCode(EqualityContract),
EqualityComparer<T1>.Default.GetHashCode(P1));
}
}
class R2 : R1, IEquatable<R2>
{
public T2 P2 { get; init; }
protected override Type EqualityContract => typeof(R2);
public override bool Equals(object? obj) => Equals(obj as R2);
public sealed override bool Equals(R1? other) => Equals((object?)other);
public virtual bool Equals(R2? other)
{
return base.Equals((R1?)other) &&
EqualityComparer<T2>.Default.Equals(P2, other.P2);
}
public static bool operator==(R2? left, R2? right)
=> (object)left == right || (left?.Equals(right) ?? false);
public static bool operator!=(R2? left, R2? right)
=> !(left == right);
public override int GetHashCode()
{
return HashCode.Combine(base.GetHashCode(),
EqualityComparer<T2>.Default.GetHashCode(P2));
}
}
class R3 : R2, IEquatable<R3>
{
public T3 P3 { get; init; }
protected override Type EqualityContract => typeof(R3);
public override bool Equals(object? obj) => Equals(obj as R3);
public sealed override bool Equals(R2? other) => Equals((object?)other);
public virtual bool Equals(R3? other)
{
return base.Equals((R2?)other) &&
EqualityComparer<T3>.Default.Equals(P3, other.P3);
}
public static bool operator==(R3? left, R3? right)
=> (object)left == right || (left?.Equals(right) ?? false);
public static bool operator!=(R3? left, R3? right)
=> !(left == right);
public override int GetHashCode()
{
return HashCode.Combine(base.GetHashCode(),
EqualityComparer<T3>.Default.GetHashCode(P3));
}
}
Kopírování a klonování členů
Typ záznamu obsahuje dva členy kopírování:
- Konstruktor přebírá jeden argument typu záznamu. Označuje se jako "kopírovací konstruktor".
- Syntetizovaná veřejná metoda instance "clone" bez parametrů s názvem rezervovaným kompilátorem
Účelem konstruktoru kopírování je zkopírovat stav z parametru do nové instance, která se vytváří. Tento konstruktor nespustí žádné inicializátory instance nebo vlastnosti, které jsou přítomné v deklaraci záznamu. Pokud konstruktor není explicitně deklarován, konstruktor bude syntetizován kompilátorem. Pokud je záznam zapečetěný, konstruktor bude soukromý, jinak bude chráněn. Explicitně deklarovaný konstruktor kopírování musí být veřejný nebo chráněný, pokud není záznam zapečetěn. První věcí, kterou musí konstruktor provést, je volat kopírovací konstruktor základní třídy nebo parametrický konstruktor objektu, pokud záznam dědí z objektu. Pokud konstruktor kopírování definovaný uživatelem používá implicitní nebo explicitní inicializátor konstruktoru, který tento požadavek nesplňuje, zobrazí se chyba. Po vyvolání kopírovacího konstruktoru základní třídy syntetizovaný kopírovací konstruktor zkopíruje hodnoty pro všechna pole instance implicitně nebo explicitně deklarovaná v záznamovém typu. Výhradní přítomnost konstruktoru kopírování bez ohledu na to, jestli je explicitní nebo implicitní, nebrání automatickému přidání výchozího konstruktoru instance.
Pokud v základním záznamu existuje virtuální metoda klonování, syntetizovaná metoda clone ji přepíše a návratový typ metody je aktuální obsahující typ. Pokud je metoda klonování základního záznamu zapečetěná, dojde k chybě. Pokud v základním záznamu není k dispozici virtuální metoda klonování, návratový typ klonovací metody je obsahující typ a metoda je virtuální, pokud záznam není zapečetěný nebo abstraktní. Pokud je obsahující záznam abstraktní, syntetizovaná klonovací metoda je také abstraktní. Pokud metoda clone není abstraktní, vrátí výsledek volání copy konstruktoru.
Tisk členů: Metody PrintMembers a ToString
Pokud je záznam odvozen z object
, záznam obsahuje syntetizovanou metodu ekvivalentní metodě deklarované následujícím způsobem:
bool PrintMembers(System.Text.StringBuilder builder);
Metoda je private
, pokud je typ záznamu sealed
. Jinak je metoda virtual
a protected
.
Metoda:
- volá metodu
System.Runtime.CompilerServices.RuntimeHelpers.EnsureSufficientExecutionStack()
pokud je metoda přítomna a záznam má vytisknoutelné členy. - pro každý tisknutelný člen záznamu (nestatické veřejné pole a čitelné členy vlastností), připojí název daného člena následovaný " = " následovaný hodnotou člena oddělenou ", ", ",
- vrátí hodnotu true, pokud záznam obsahuje tisknutelné č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. Nyní to znamená volání ToString
před předáním do StringBuilder.Append
.
Pokud je typ záznamu odvozen ze základního záznamu Base
, záznam obsahuje syntetizované přepsání ekvivalentní metodě deklarované takto:
protected override bool PrintMembers(StringBuilder builder);
Pokud záznam nemá žádné tisknutelné členy, metoda volá základní metodu PrintMembers
s jedním argumentem (se svým parametrem builder
) a vrací výsledek.
V opačném případě metoda:
- volá základní
PrintMembers
metodu s jedním argumentem (jehobuilder
parametrem). - pokud metoda
PrintMembers
vrátila hodnotu true, připojte ", " k builderu, - pro každý tisknutelný člen záznamu připojí název daného člena následovaný " = " a poté hodnotou člena:
this.member
(nebothis.member.ToString()
pro typy hodnot), odděleným ", " - vrátí hodnotu true.
Metodu PrintMembers
lze deklarovat explicitně.
Jedná se o chybu, pokud explicitní deklarace neodpovídá očekávanému podpisu nebo přístupnosti, nebo pokud neumožňuje přepsání v odvozeném typu a typ záznamu není sealed
.
Záznam obsahuje syntetizovanou metodu ekvivalentní metodě deklarované následujícím způsobem:
public override string ToString();
Metodu lze deklarovat explicitně. Jedná se o chybu, pokud explicitní deklarace neodpovídá očekávanému podpisu nebo přístupnosti, nebo pokud neumožňuje přepsání v odvozeném typu a typ záznamu není sealed
. Jedná se o chybu, pokud syntetizovaná nebo explicitně deklarovaná metoda nepřepíše object.ToString()
(například kvůli stínování v mezilehlých základních typech atd.).
Syntetizovaná metoda:
- vytvoří instanci
StringBuilder
, - připojí název záznamu ke konstruktoru, následovaný znakem " { ",
- vyvolá metodu
PrintMembers
záznamu, která ho dává tvůrci, a potom " " pokud vrátil hodnotu true, - připojí "}",
- vrátí obsah sestavovače s
builder.ToString()
.
Představte si například následující typy záznamů:
record R1(T1 P1);
record R2(T1 P1, T2 P2, T3 P3) : R1(P1);
U těchto typů záznamů by tiskové součástky vytvořené syntézou vypadaly přibližně takto:
class R1 : IEquatable<R1>
{
public T1 P1 { get; init; }
protected virtual bool PrintMembers(StringBuilder builder)
{
builder.Append(nameof(P1));
builder.Append(" = ");
builder.Append(this.P1); // or builder.Append(this.P1.ToString()); if T1 is 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();
}
}
class R2 : R1, IEquatable<R2>
{
public T2 P2 { get; init; }
public T3 P3 { get; init; }
protected override bool PrintMembers(StringBuilder builder)
{
if (base.PrintMembers(builder))
builder.Append(", ");
builder.Append(nameof(P2));
builder.Append(" = ");
builder.Append(this.P2); // or builder.Append(this.P2); if T2 is a value type
builder.Append(", ");
builder.Append(nameof(P3));
builder.Append(" = ");
builder.Append(this.P3); // or builder.Append(this.P3); if T3 is a value type
return true;
}
public override string ToString()
{
var builder = new StringBuilder();
builder.Append(nameof(R2));
builder.Append(" { ");
if (PrintMembers(builder))
builder.Append(" ");
builder.Append("}");
return builder.ToString();
}
}
Členové pozičních záznamů
Kromě výše uvedených členů záznamy se seznamem parametrů ("poziční záznamy") syntetizují další členy se stejnými podmínkami jako výše uvedené členy.
Primární konstruktor
Typ záznamu má veřejný konstruktor, jehož podpis odpovídá parametrům hodnoty deklarace typu. Toto se nazývá primární konstruktor pro daný typ a způsobí, že implicitně deklarovaný výchozí konstruktor třídy, pokud existuje, je potlačen. Je chybou mít primární konstruktor a konstruktor se stejným podpisem, který již ve třídě existuje.
Během provádění programu primární konstruktor
spustí inicializátory instancí vyskytující se v těle třídy.
vyvolá konstruktor základní třídy s argumenty zadanými v klauzuli
record_base
, pokud je k dispozici.
Pokud má záznam primární konstruktor, jakýkoli uživatelem definovaný konstruktor s výjimkou "kopírovacího konstruktoru" musí mít inicializátor konstruktora explicitní this
.
Parametry primárního konstruktoru i členů záznamu jsou k dispozici v rámci klauzule argument_list
record_base
a v inicializátorech polí nebo vlastností instance. Používání členů instance by bylo v těchto místech chybou (podobně jako je dnes chybou používat členy instance v běžných inicializátorech konstruktoru), ale parametry primárního konstruktoru by byly v oboru, byly použitelné a překrývaly by členy. Statické členy by také bylo možné použít, podobně jako základní volání a inicializátory fungují v běžných konstruktorech dnes.
Upozornění se vytvoří, pokud není přečtený parametr primárního konstruktoru.
Proměnné výrazů deklarované v argument_list
jsou dostupné v argument_list
. Platí stejná pravidla stínování jako v seznamu argumentů inicializátoru regulárního konstruktoru.
Vlastnosti
Pro každý parametr záznamu deklarace typu záznamu existuje odpovídající člen veřejné vlastnosti, jehož název a typ jsou převzaty z deklarace parametru hodnoty.
Pro záznam:
- Vytvoří se veřejná automatická vlastnost
get
ainit
(viz samostatná specifikace přístupuinit
). Zděděná vlastnostabstract
s odpovídajícím typem se přepíše. Jedná se o chybu, pokud zděděná vlastnost nemá přepisovatelné přístupypublic
,get
ainit
. Jedná se o chybu, pokud je zděděná vlastnost 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 jejich podpůrné pole pomocí cílůproperty:
nebofield:
pro atributy syntakticky aplikované na odpovídající parametr záznamu.
Dekonstrukce
Poziční záznam s alespoň jedním parametrem syntetizuje veřejnou metodu instance vracející void s názvem 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ému parametru Deconstruct
metody, hodnotu vlastnosti instance stejného názvu.
Metodu lze deklarovat explicitně. Jedná se o chybu, pokud explicitní deklarace neodpovídá očekávanému podpisu nebo přístupnosti nebo je statická.
Následující příklad ukazuje poziční záznam R
s metodou Deconstruct
syntetizovanou kompilátorem a jeho využitím:
public record R(int P1, string P2 = "xyz")
{
public void Deconstruct(out int P1, out string P2)
{
P1 = this.P1;
P2 = this.P2;
}
}
class Program
{
static void Main()
{
R r = new R(12);
(int p1, string p2) = r;
Console.WriteLine($"p1: {p1}, p2: {p2}");
}
}
výraz with
Výraz with
je nový výraz s následující syntaxí.
with_expression
: switch_expression
| switch_expression 'with' '{' member_initializer_list? '}'
;
member_initializer_list
: member_initializer (',' member_initializer)*
;
member_initializer
: identifier '=' expression
;
Výraz with
není povolen jako příkaz.
Výraz with
umožňuje "nedestruktivní mutaci", která je navržená tak, aby vytvářela kopii výrazu příjemce s úpravami přiřazení v member_initializer_list
.
Platný výraz with
má přijímač s jiným typem než void. Typ přijímače musí být záznam.
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.
Nejprve se vyvolá metoda "clone" příjemce (zadaná výše) a její výsledek se převede na typ příjemce. Potom se každý member_initializer
zpracuje stejným způsobem jako přiřazení k poli nebo přístup k vlastnosti výsledku převodu. Přiřazení se zpracovávají v lexikálním pořadí.
C# feature specifications