Struktury rekordów
Notatka
Ten artykuł jest specyfikacją funkcji. Specyfikacja służy jako dokument projektowy dla funkcji. Zawiera proponowane zmiany specyfikacji wraz z informacjami wymaganymi podczas projektowania i opracowywania funkcji. Te artykuły są publikowane do momentu sfinalizowania proponowanych zmian specyfikacji i włączenia ich do obecnej specyfikacji ECMA.
Mogą wystąpić pewne rozbieżności między specyfikacją funkcji a ukończoną implementacją. Te różnice są przechwytywane w odpowiednich spotkania projektowego języka (LDM).
Więcej informacji na temat procesu wdrażania specyfikacji funkcji można znaleźć w standardzie języka C# w artykule dotyczącym specyfikacji .
Problem z mistrzem: https://github.com/dotnet/csharplang/issues/4334
Składnia struktury rekordu jest następująca:
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 struktur rekordów to typy wartości, takie jak inne typy struktur. Niejawnie dziedziczą z klasy System.ValueType
.
Modyfikatory i elementy członkowskie struktury rekordów podlegają tym samym ograniczeniom co te w strukturach (dostępność typu, modyfikatory elementów członkowskich, inicjatory konstruktorów wystąpienia base(...)
, określone przypisanie dla this
w konstruktorze, destruktorach, ...). Struktury rekordów będą również zgodne z tymi samymi zasadami co struktury pod względem konstruktorów wystąpienia bez parametrów i inicjatorów pól, ale w tym dokumencie zakłada się, że te ograniczenia zostaną zniesione dla struktur ogólnie.
Zobacz §16.4.9 Zobacz konstruktory struktury bez parametrów specyfikacji.
Struktury rekordów nie mogą używać modyfikatora ref
.
Co najwyżej jedna częściowa deklaracja typu częściowej struktury rekordów może dostarczyć parameter_list
.
parameter_list
może być pusta.
Parametry struktury rekordów nie mogą używać ref
, modyfikatorów out
lub this
(ale dozwolone są in
i params
).
Elementy struktury rekordowej
Oprócz składowych zadeklarowanych w treści struktury rekordu typ struktury rekordu ma dodatkowe syntetyzowane elementy członkowskie. Składowe są syntetyzowane, chyba że składowa z "pasującym" podpisem jest zadeklarowana w treści struktury rekordu lub gdy dziedziczona jest dostępna konkretna niewirtualna składowa z "pasującym" podpisem. Dwa elementy członkowie są uważane za zgodne, jeśli mają tę samą sygnaturę lub będą uznane za "ukrywające" w scenariuszu dziedziczenia. Zobacz Podpisy i przeciążenie §7.6. Jest błędem, gdy członek struktury rekordowej ma nazwę "Clone".
Błędem jest, gdy pole instancji struktury rekordu ma niebezpieczny typ.
Struktura rekordów nie może zadeklarować destruktora.
Syntetyzowane składowe są następujące:
Członkowie na rzecz równości
Syntetyzowane składowe równości są podobne do tych w klasie rekordów (Equals
dla tego typu, Equals
dla typu object
, operatorzy ==
i !=
dla tego typu),
z wyjątkiem braku EqualityContract
, sprawdzania wartości null lub dziedziczenia.
Struktura rekordów implementuje System.IEquatable<R>
i zawiera syntetyzowane silnie typizowane przeciążenie Equals(R other)
, gdzie R
jest strukturą rekordu.
Metoda jest public
.
Metodę można zadeklarować jawnie. Jest to błąd, jeśli jawna deklaracja nie jest zgodna z oczekiwanym podpisem lub ułatwieniami dostępu.
Jeśli Equals(R other)
jest definiowana przez użytkownika (nie jest syntetyzowana), ale GetHashCode
nie, zostanie wygenerowane ostrzeżenie.
public readonly bool Equals(R other);
Syntetyzowana Equals(R)
zwraca true
, jeśli i tylko wtedy, gdy dla każdego pola wystąpienia fieldN
w strukturze rekordowej wartość System.Collections.Generic.EqualityComparer<TN>.Default.Equals(fieldN, other.fieldN)
, gdzie TN
jest typem pola, wynosi true
.
Struktura rekordów zawiera syntetyzowane operatory ==
i !=
równoważne operatorom zadeklarowanych w następujący sposób:
public static bool operator==(R r1, R r2)
=> r1.Equals(r2);
public static bool operator!=(R r1, R r2)
=> !(r1 == r2);
Metoda Equals
wywoływana przez operator ==
jest metodą Equals(R other)
określoną powyżej. Operator !=
deleguje do operatora ==
. Jest to błąd, jeśli operatory są zadeklarowane jawnie.
Struktura rekordów zawiera syntetyzowane zastąpienie równoważne metodzie zadeklarowanej w następujący sposób:
public override readonly bool Equals(object? obj);
Jest to błąd, jeśli zastąpienie jest zadeklarowane jawnie.
Syntetyzowane nadpisanie zwraca other is R temp && Equals(temp)
, gdzie R
jest strukturą rekordu.
Struktura rekordów zawiera syntetyzowane zastąpienie równoważne metodzie zadeklarowanej w następujący sposób:
public override readonly int GetHashCode();
Metodę można zadeklarować jawnie.
Ostrzeżenie jest zgłaszane, jeśli jeden z Equals(R)
i GetHashCode()
jest jawnie zadeklarowany, ale druga metoda nie jest jawna.
Syntetyzowane zastąpienie GetHashCode()
zwraca int
wynik połączenia wartości System.Collections.Generic.EqualityComparer<TN>.Default.GetHashCode(fieldN)
dla każdego pola wystąpienia fieldN
z TN
typem fieldN
.
Rozważmy na przykład następującą strukturę rekordów:
record struct R1(T1 P1, T2 P2);
W przypadku tej struktury rekordów syntetyzowani członkowie równości będą jak poniżej:
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));
}
}
Członkowie drukowania: metody PrintMembers i ToString
Struktura rekordów zawiera metodę syntetyzowana równoważną metodzie zadeklarowanej w następujący sposób:
private bool PrintMembers(System.Text.StringBuilder builder);
Metoda wykonuje następujące czynności:
- Dla każdego z elementów członkowskich, które można wydrukować w strukturze rekordu (niestatyczne pola publiczne i czytelne właściwości), dołącza nazwę tego elementu członkowskiego, po której następuje " = ", a następnie wartość elementu członkowskiego oddzielona ", ".
- zwraca true, jeśli rekord zawiera elementy, które można drukować.
W przypadku elementu członkowskiego, który ma typ wartości, przekonwertujemy jego wartość na reprezentację ciągu przy użyciu najbardziej wydajnej metody dostępnej dla platformy docelowej. Obecnie oznacza to wywołanie ToString
przed przekazaniem do StringBuilder.Append
.
Jeśli drukowalne elementy członkowskie rekordu nie zawierają czytelnej właściwości z akcesorem innym niżreadonly
get
, syntetyzowana PrintMembers
jest readonly
. Nie jest konieczne, aby pola rekordu były readonly
, aby metoda PrintMembers
była readonly
.
Metodę PrintMembers
można zadeklarować jawnie.
Jest to błąd, jeśli jawna deklaracja nie jest zgodna z oczekiwanym podpisem lub ułatwieniami dostępu.
Struktura rekordów zawiera metodę syntetyzowana równoważną metodzie zadeklarowanej w następujący sposób:
public override string ToString();
Jeśli metoda PrintMembers
struktury rekordu jest readonly
, metoda syntetyzowana ToString()
jest readonly
.
Metodę można zadeklarować jawnie. Jest to błąd, jeśli jawna deklaracja nie jest zgodna z oczekiwanym podpisem lub ułatwieniami dostępu.
Metoda syntetyzowana:
- tworzy wystąpienie
StringBuilder
, - Dołącza nazwę struktury rekordu do konstruktora, a następnie " { ",
- wywołuje metodę
PrintMembers
struktury rekordu, przekazując jej budowniczy, po czym, jeśli zwróciła wartość true, dodaje " ". - dołącza "}",
- Zwraca zawartość konstruktora z
builder.ToString()
.
Rozważmy na przykład następującą strukturę rekordów:
record struct R1(T1 P1, T2 P2);
W przypadku tej struktury rekordu, syntetyzowane elementy członkowskie do wyświetlania będą prezentować się następująco:
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();
}
}
Elementy składowe struktury rekordów pozycyjnych
Oprócz powyższych członków, rekordy struktury z listą parametrów ("rekordy pozycyjne") syntetyzują dodatkowych członków z tymi samymi warunkami co członkowie powyżej.
Konstruktor podstawowy
Struktura rekordów ma publiczny konstruktor, którego podpis odpowiada parametrom wartości deklaracji typu. Jest to nazywane konstruktorem podstawowym dla typu. Jest błędem posiadanie podstawowego konstruktora, jeśli w strukturze jest już obecny konstruktor o tym samym podpisie. Jeśli deklaracja typu nie zawiera listy parametrów, nie jest generowany żaden podstawowy konstruktor.
record struct R1
{
public R1() { } // ok
}
record struct R2()
{
public R2() { } // error: 'R2' already defines constructor with same parameter types
}
Deklaracje pól wystąpienia dla struktury rekordów mogą zawierać inicjatory zmiennych. Jeśli nie ma podstawowego konstruktora, inicjatory wystąpień są wykonywane jako część konstruktora bez parametrów. W przeciwnym razie w czasie wykonywania podstawowy konstruktor wykonuje inicjatory wystąpień pojawiające się w treści struct-body rekordu.
Jeśli rekordowa struktura ma główny konstruktor, każdy konstruktor zdefiniowany przez użytkownika musi mieć jawny inicjalizator konstruktora this
, który wywołuje główny konstruktor lub konstruktor zadeklarowany jawnie.
Parametry konstruktora podstawowego oraz członkowie struktury rekordu są dostępne w inicjalizatorach pól instancji lub właściwości. Członkowie instancji byliby błędem w tych miejscach odniesienia kodu (podobnie jak członkowie instancji są w zakresie w zwykłych inicjatorach konstruktorów dzisiaj, ale błędem, jeśli użyte), ale parametry konstruktora podstawowego byłyby dostępne i możliwe do użycia i przesłaniałyby członków. Statyczne elementy członkowskie byłyby również możliwe do użycia.
Ostrzeżenie jest generowane, jeśli parametr podstawowego konstruktora nie jest odczytywany.
Reguły przypisania ostatecznego dla konstruktorów instancji struktur mają zastosowanie do podstawowego konstruktora struktur rekordowych. Na przykład następujący błąd:
record struct Pos(int X) // definite assignment error in primary constructor
{
private int x;
public int X { get { return x; } set { x = value; } } = X;
}
Właściwości
Dla każdego parametru struktury rekordu w deklaracji struktury rekordu istnieje odpowiadająca mu publiczna właściwość, której nazwa i typ pochodzą z deklaracji parametru wartości.
W przypadku struktury rekordów:
- Zostanie utworzona publiczna właściwość automatyczna
get
iinit
, jeśli rekordowa struktura ma modyfikatorreadonly
; w przeciwnym razieget
iset
. Oba rodzaje zestawów metod dostępu (set
iinit
) są uważane za "pasujące". W związku z tym użytkownik może zadeklarować właściwość tylko do inicjowania zamiast syntetyzowanej właściwości modyfikowalnej. Dziedziczona właściwośćabstract
o pasującym typie jest zastępowana. Nie jest tworzona żadna właściwość automatyczna, jeśli struktura rekordów ma pole wystąpienia o oczekiwanej nazwie i typie. Jest to błąd, jeśli dziedziczona właściwość nie mapublic
get
iset
/init
metod dostępu. Jest to błąd, jeśli dziedziczona właściwość lub pole jest ukryte.
Właściwość automatyczna jest inicjowana do wartości odpowiadającego podstawowego parametru konstruktora. Atrybuty można stosować do syntetyzowanej właściwości automatycznej i pola zapasowego przy użyciuproperty:
lubfield:
obiektów docelowych dla atrybutów składniowo zastosowanych do odpowiedniego parametru struktury rekordu.
Dekonstrukcja
Struktura rekordów pozycyjnych z co najmniej jednym parametrem syntetyzuje publiczną metodę instancyjną, która zwraca void, o nazwie Deconstruct
, z deklaracją parametru out dla każdego parametru deklaracji konstruktora podstawowego. Każdy parametr metody Deconstruct ma ten sam typ co odpowiedni parametr deklaracji konstruktora podstawowego. Treść metody przypisuje każdy parametr metody Deconstruct do wartości uzyskanej z dostępu do członka obiektu o tej samej nazwie.
Jeśli członkowie wystąpienia są dostępne w treści i nie zawierają właściwości z akcesorem innym niżreadonly
get
, to metodą zsyntetyzowaną Deconstruct
jest readonly
.
Metodę można zadeklarować jawnie. Jest to błąd, jeśli jawna deklaracja nie jest zgodna z oczekiwanym podpisem lub ułatwieniami dostępu lub jest statyczna.
Zezwalaj na wyrażenie with
w strukturach
Obecnie dla odbiornika w wyrażeniu with
dopuszczalne jest posiadanie typu struktury.
Po prawej stronie wyrażenia with
znajduje się member_initializer_list
z sekwencją przypisań do identyfikatora ,, które musi być dostępnym polem instancyjnym lub właściwością typu odbiorcy.
W przypadku odbiornika o typie struktury, odbiornik jest najpierw kopiowany, a następnie każdy member_initializer
jest przetwarzany w taki sam sposób, jak wykonanie przypisania do pola lub uzyskanie dostępu do właściwości wyniku konwersji.
Zadania są przetwarzane w kolejności leksykalnej.
Ulepszenia rekordów
Zezwalaj na record class
Istniejąca składnia typów rekordów umożliwia record class
z tym samym znaczeniem co record
:
record_declaration
: attributes? class_modifier* 'partial'? 'record' 'class'? identifier type_parameter_list?
parameter_list? record_base? type_parameter_constraints_clause* record_body
;
Zezwalaj na to, aby członkowie pozycyjni zdefiniowani przez użytkownika stali się polami
Nie jest tworzona żadna właściwość automatyczna, jeśli rekord posiada lub dziedziczy pole instancji o oczekiwanej nazwie i typie.
Umożliwiaj konstruktory bez parametrów i inicjatory składowych w strukturach
Zobacz specyfikację konstruktorów struktury bez parametrów.
Otwórz pytania
- jak rozpoznawać struktury rekordów w metadanych? (Nie mamy niewyokreślonej metody klonowania do wykorzystania...)
Odpowiedział
- upewnij się, że chcemy zachować projekt PrintMembers (oddzielna metoda zwracająca
bool
) (odpowiedź: tak) - Potwierdź, że nie zezwolimy na
record ref struct
(problem z polamiIEquatable<RefStruct>
i ref) (odpowiedź: tak) - potwierdzanie implementacji elementów członkowskich równości. Alternatywą jest to, że zsyntetyzowane
bool Equals(R other)
,bool Equals(object? other)
i operatory tylko delegują doValueType.Equals
. (Odpowiedź: tak) - Upewnij się, że chcemy pozwolić na użycie inicjatorów pól, gdy istnieje główny konstruktor. Czy chcemy również zezwolić na konstruktory bezparametrowe struktur przy okazji (problem z aktywatorem został najwyraźniej rozwiązany)? (odpowiedź: Tak, zaktualizowana specyfikacja powinna zostać poddana przeglądowi w LDM)
- ile chcemy powiedzieć o metodzie
Combine
? (odpowiedź: jak najmniej) - czy powinniśmy zabronić konstruktorowi zdefiniowanemu przez użytkownika mieć sygnaturę konstruktora kopii? (odpowiedź: nie, nie ma pojęcia konstruktora kopiowania w specyfikacji struktur rekordów)
- upewnij się, że chcemy zakazać członkom używania nazwy "Clone". (odpowiedź: poprawna)
- Podwójnie sprawdź, czy zsyntetyzowana logika
Equals
jest funkcjonalnie równoważna implementacji podczas wykonywania (np. float.NaN) (odpowiedź: potwierdzona w LDM). - czy atrybuty celujące w pole lub celujące w właściwość można umieścić na liście parametrów pozycyjnych? (odpowiedź: tak, tak samo jak w przypadku klasy rekordowej)
-
with
w przypadku typów ogólnych? (odpowiedź: poza zakresem języka C# 10) - czy
GetHashCode
powinien zawierać skrót samego typu, aby uzyskać różne wartości międzyrecord struct S1;
arecord struct S2;
? (odpowiedź: nie)
C# feature specifications