Datensatzstrukturen
Anmerkung
Dieser Artikel ist eine Featurespezifikation. Die Spezifikation dient als Designdokument für das Feature. Es enthält vorgeschlagene Spezifikationsänderungen sowie Informationen, die während des Entwurfs und der Entwicklung des Features erforderlich sind. Diese Artikel werden veröffentlicht, bis die vorgeschlagenen Spezifikationsänderungen abgeschlossen und in die aktuelle ECMA-Spezifikation aufgenommen werden.
Es kann einige Abweichungen zwischen der Featurespezifikation und der abgeschlossenen Implementierung geben. Diese Unterschiede sind in den entsprechenden Hinweisen zum Language Design Meeting (LDM) festgehalten.
Mehr über den Prozess der Adaption von Funktionen in den C#-Sprachstandard erfahren Sie in dem Artikel über die Spezifikationen.
Die Syntax für einen Datensatz-Struct lautet wie folgt:
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
| ';'
;
Datensatzstrukturtypen sind Werttypen, wie andere Strukturtypen. Sie erben implizit von der Klasse System.ValueType
.
Die Modifikatoren und Mitglieder eines Datensatz-Struct unterliegen den gleichen Einschränkungen wie die von Structs (Zugriffsfreiheit auf Typ, Modifikatoren auf Mitglieder, base(...)
Instanz-Konstruktor-Initialisierer, definitive Zuweisung für this
im Konstruktor, Destruktoren ...). Datensatz-Structs folgen auch den gleichen Regeln wie Structs für parameterlose Instanz-Konstruktoren und Feldinitialisierer, aber dieses Dokument geht davon aus, dass wir diese Beschränkungen für Structs allgemein aufheben werden.
Siehe §16.4.9 Siehe parameterlose Struct-Konstruktoren Spezifikation.
Datensatz-Structs können den Modifikator ref
nicht verwenden.
Höchstens eine partielle Typdeklaration einer partiellen Datensatz-Struct darf ein parameter_list
enthalten.
Das parameter_list
kann leer sein.
Datensatz-Struct-Parameter können keine ref
, out
oder this
Modifikatoren verwenden (in
und params
sind jedoch zulässig).
Mitglieder einer Datensatz-Struct
Zusätzlich zu den Mitgliedern, die im Datensatzstrukturkörper deklariert sind, verfügt ein Datensatzstrukturtyp über zusätzliche synthetisierte Mitglieder. Mitglieder werden synthetisiert, es sei denn, ein Mitglied mit einer "übereinstimmenden" Signatur wird im Body der Datensatz-Struct deklariert oder ein zugreifbares konkretes nicht-virtuelles Mitglied mit einer "übereinstimmenden" Signatur wird vererbt. Zwei Mitglieder werden als übereinstimmend betrachtet, wenn sie die gleiche Signatur haben oder in einem Vererbungsszenario als "versteckt" betrachtet werden würden. Siehe Signaturen und Überladung §7.6. Es ist ein Fehler, wenn ein Mitglied einer Datensatz-Struct "Clone" genannt wird.
Es ist ein Fehler, wenn ein Instanz-Feld einer Datensatz-Struct einen unsicheren Typ hat.
Ein Datensatz-Struct darf keinen Destruktor deklarieren.
Die synthetisierten Mitglieder sind wie folgt:
Equality-Mitglieder
Die synthetisierten Equality-Mitglieder sind ähnlich wie in einer Datensatzklasse (Equals
für diesen Typ, Equals
für object
Typ, ==
und !=
Operatoren für diesen Typ),
bis auf das Fehlen von EqualityContract
, Nullprüfungen oder Vererbung.
Die Datensatz-Struct implementiert System.IEquatable<R>
und enthält eine synthetisierte, stark typisierte Überladung von Equals(R other)
, wobei R
die Datensatz-Struct ist.
Die Methode ist public
.
Die Methode kann explizit deklariert werden. Es ist ein Fehler, wenn die explizite Deklaration nicht mit der erwarteten Signatur oder Zugänglichkeit übereinstimmt.
Wenn Equals(R other)
benutzerdefiniert (nicht synthetisiert) ist, aber GetHashCode
es nicht ist, wird eine Warnung erzeugt.
public readonly bool Equals(R other);
Die synthetisierte Equals(R)
gibt true
zurück, wenn und nur wenn für jedes Instanzfeld fieldN
in der Datensatz-Struct der Wert von System.Collections.Generic.EqualityComparer<TN>.Default.Equals(fieldN, other.fieldN)
, wobei TN
der Feldtyp ist, true
ist.
Die Datensatz-Struct enthält synthetisierte ==
- und !=
-Operatoren, die den wie folgt deklarierten Operatoren entsprechen:
public static bool operator==(R r1, R r2)
=> r1.Equals(r2);
public static bool operator!=(R r1, R r2)
=> !(r1 == r2);
Die Equals
Methode, die vom ==
-Operator aufgerufen wird, ist die oben angegebene Equals(R other)
Methode. Der !=
-Operator delegiert an den ==
-Operator. Es ist ein Fehler, wenn die Operatoren explizit deklariert werden.
Die Datensatz-Struct enthält eine synthetisierte Überschreibung, die einer wie folgt deklarierten Methode entspricht:
public override readonly bool Equals(object? obj);
Es ist ein Fehler, wenn die Überschreibung explizit deklariert wird.
Der synthetisierte Override gibt other is R temp && Equals(temp)
zurück, wobei R
die Datensatz-Struct ist.
Die Datensatz-Struct enthält eine synthetisierte Überschreibung, die einer wie folgt deklarierten Methode entspricht:
public override readonly int GetHashCode();
Die Methode kann explizit deklariert werden.
Eine Warnung wird geliefert, wenn eine der Methoden Equals(R)
und GetHashCode()
explizit deklariert ist, die andere Methode jedoch nicht explizit.
Die synthetisierte Überschreibung von GetHashCode()
gibt ein int
-Ergebnis der Kombination der Werte von System.Collections.Generic.EqualityComparer<TN>.Default.GetHashCode(fieldN)
für jedes Instanzfeld fieldN
zurück, wobei TN
der Typ von fieldN
ist.
Betrachten Sie zum Beispiel die folgende Datensatz-Struct:
record struct R1(T1 P1, T2 P2);
Für diese Datensatz-Struct würden die synthetisierten Equality-Mitglieder etwa so aussehen:
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));
}
}
Druck-Mitglieder: PrintMembers- und ToString-Methoden
Die Datensatzstruktur enthält eine synthetisierte Methode, die einer wie folgt deklarierten Methode entspricht:
private bool PrintMembers(System.Text.StringBuilder builder);
Die Methode tut Folgendes:
- fügt für jedes Druck-Mitglied des Datensatz-Structs (nicht statisches öffentliches Feld und lesbare Eigenschaftsmitglieder) den Namen des Mitglieds an, gefolgt von " = " und dem Wert des Mitglieds, getrennt durch ", ",
- gibt true zurück, wenn die Datensatz-Struct Druck-Mitglieder hat.
Für ein Mitglied mit einem Werttyp wandeln wir seinen Wert mithilfe der effizientesten Methode, die für die Zielplattform verfügbar ist, in eine Zeichenfolgendarstellung um. Zur Zeit bedeutet das, dass Sie ToString
aufrufen, bevor Sie StringBuilder.Append
übergeben.
Wenn die Druck-Mitglieder des Datensatzes keine lesbare Eigenschaft mit einem Non-readonly
get
-Accessor enthalten, dann ist der synthetisierte PrintMembers
readonly
. Es ist nicht erforderlich, dass die Felder des Datensatzes readonly
sind, damit die Methode PrintMembers
readonly
ist.
Die PrintMembers
-Methode kann explizit deklariert werden.
Es ist ein Fehler, wenn die explizite Deklaration nicht mit der erwarteten Signatur oder Zugänglichkeit übereinstimmt.
Die Datensatzstruktur enthält eine synthetisierte Methode, die einer wie folgt deklarierten Methode entspricht:
public override string ToString();
Wenn die PrintMembers
Methode der Datensatz-Struct readonly
ist, dann ist die synthetisierte ToString()
-Methode readonly
.
Die Methode kann explizit deklariert werden. Es ist ein Fehler, wenn die explizite Deklaration nicht mit der erwarteten Signatur oder Zugänglichkeit übereinstimmt.
Die synthetisierte Methode:
- erstellt eine
StringBuilder
-Instanz, - fügt den Namen der Datensatz-Struct an den Builder an, gefolgt von " { ",
- ruft die
PrintMembers
-Methode des Datensatz-Structs auf und übergibt ihm den Builder, gefolgt von " ", wenn sie true zurückgibt, - fügt "}" an,
- gibt den Inhalt des Builders mit
builder.ToString()
zurück.
Betrachten Sie zum Beispiel die folgende Datensatz-Struct:
record struct R1(T1 P1, T2 P2);
Für diesen Datensatz-Struct würden die synthetischen Druck-Mitglieder etwa so aussehen:
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();
}
}
Positionale Datensatz-Struct-Mitglieder
Zusätzlich zu den oben genannten Mitgliedern synthetisieren Datensatz-Structs mit einer Parameterliste ("Positionsdatensätze") weitere Mitglieder mit den gleichen Bedingungen wie die oben genannten Mitglieder.
Primärer Konstruktor
Eine Datensatzstruktur weist einen öffentlichen Konstruktor auf, dessen Signatur den Wertparametern der Typdeklaration entspricht. Dies wird der primäre Konstruktor für den Typ genannt. Es ist ein Fehler, wenn ein primärer Konstruktor und ein Konstruktor mit der gleichen Signatur bereits in der Struct vorhanden sind. Wenn die Typdeklaration keine Parameterliste enthält, wird kein primärer Konstruktor generiert.
record struct R1
{
public R1() { } // ok
}
record struct R2()
{
public R2() { } // error: 'R2' already defines constructor with same parameter types
}
Instanz-Felddeklarationen für eine Datensatz-Struct dürfen Variableninitialisierer enthalten. Wenn kein primärer Konstruktor vorhanden ist, werden die Instanzinitialisierer als Teil des parameterlosen Konstruktors ausgeführt. Andernfalls führt der primäre Konstruktor zur Laufzeit die Instanz-Initialisierer aus, die im Datensatz-Struct-Body erscheinen.
Wenn eine Datensatzstruktur einen primären Konstruktor hat, muss ein benutzerdefinierter Konstruktor einen expliziten this
-Konstruktor-Initialisierer haben, der entweder den primären oder einen explizit deklarierten Konstruktor aufruft.
Parameter des primären Konstruktors sowie Mitglieder der Datensatz-Struct befinden sich im Bereich der Initialisierer von Instanzfeldern oder -eigenschaften. Instanzmitglieder wären an diesen Orten ein Fehler (ähnlich wie Instanzmitglieder heute in regulären Konstruktorinitialisierern im Bereich sind, aber ein Fehler bei der Verwendung), aber die Parameter des primären Konstruktors wären im Bereich und verwendbar und würden Mitglieder überschatten. Statische Mitglieder wären ebenfalls verwendbar.
Eine Warnung wird erzeugt, wenn ein Parameter des primären Konstruktors nicht gelesen wird.
Die definitiven Zuweisungsregeln für Konstruktoren von Struct-Instanzen gelten auch für den primären Konstruktor von Datensatz-Structs. Beispielsweise ist folgendes ein Fehler:
record struct Pos(int X) // definite assignment error in primary constructor
{
private int x;
public int X { get { return x; } set { x = value; } } = X;
}
Eigenschaften
Für jeden Datensatzstrukturparameter einer Datensatzstrukturdeklaration gibt es ein entsprechendes öffentliches Eigenschaftselement, dessen Name und Typ aus der Wertparameterdeklaration entnommen werden.
Für eine Datensatz-Struct:
- Eine öffentliche
get
undinit
Auto-Eigenschaft wird erstellt, wenn die Datensatz-Struct den Modifikatorreadonly
hat, ansonstenget
undset
. Beide Arten von Set-Accessoren (set
undinit
) werden als "passend" betrachtet. Daher kann der Benutzer anstelle einer synthetisierten änderbaren Eigenschaft eine init-only-Eigenschaft deklarieren. Eine geerbteabstract
-Eigenschaft mit passendem Typ wird überschrieben. Es wird keine Auto-Eigenschaft erstellt, wenn die Datensatz-Struct ein Instanzfeld mit dem erwarteten Namen und Typ hat. Es ist ein Fehler, wenn die geerbte Eigenschaft nicht überpublic
get
undset
/init
-Accessoren verfügt. Es ist ein Fehler, wenn die geerbte Eigenschaft oder das Feld ausgeblendet ist.
Die Auto-Eigenschaft wird mit dem Wert des entsprechenden primären Konstruktorparameters initialisiert. Attribute können auf die synthetisierte Auto-Eigenschaft und ihr zugehöriges Feld angewendet werden, indem Sieproperty:
- oderfield:
-Ziele für Attribute verwenden, die syntaktisch auf den entsprechenden Datensatz-Struct-Parameter angewendet werden.
Dekonstruktion
Ein Datensatz-Struct mit mindestens einem Parameter synthetisiert eine öffentliche Instanz-Methode mit Rückgabewert Deconstruct
mit einer Out-Parameter-Deklaration für jeden Parameter der primären Konstruktor-Deklaration. Jeder Parameter der Deconstruct-Methode hat den gleichen Typ wie der entsprechende Parameter der primären Konstruktordeklaration. Der Body der Methode weist jedem Parameter der Deconstruct-Methode den Wert aus einem Mitgliedszugriff eines Instanzmitglieds auf ein gleichnamiges Mitglied zu.
Wenn die Instanzmitglieder, auf die im Body zugegriffen wird, keine Eigenschaft mit einem Non-readonly
get
-Accessor enthalten, dann ist die synthetisierte Deconstruct
-Methode readonly
.
Die Methode kann explizit deklariert werden. Es ist ein Fehler, wenn die explizite Deklaration nicht mit der erwarteten Signatur oder Barrierefreiheit übereinstimmt oder statisch ist.
with
-Ausdruck auf Structs zulassen
Es ist jetzt zulässig, dass der Empfänger in einem with
-Ausdruck einen Struct-Typ hat.
Auf der rechten Seite des with
-Ausdrucks befindet sich ein member_initializer_list
mit einer Sequenz von Zuweisungen an identifier, der ein zugriffsfreies Instanzfeld oder eine Eigenschaft vom Typ des Empfängers sein muss.
Bei einem Empfänger mit Struct-Typ wird der Empfänger zunächst kopiert, dann wird jedes member_initializer
auf die gleiche Weise verarbeitet wie eine Zuweisung an ein Feld oder eine Zugriffsfreiheit auf eine Eigenschaft des Ergebnisses der Konvertierung.
Zuweisungen werden in lexikalischer Reihenfolge verarbeitet.
Verbesserungen bei Datensätzen
record class
zulassen
Die bestehende Syntax für Datensätze lässt record class
mit der gleichen Bedeutung wie record
zu:
record_declaration
: attributes? class_modifier* 'partial'? 'record' 'class'? identifier type_parameter_list?
parameter_list? record_base? type_parameter_constraints_clause* record_body
;
Zulassen, dass benutzerdefinierte positionale Mitglieder zu Feldern werden
Es wird keine Auto-Eigenschaft erstellt, wenn der Datensatz ein Instanzfeld mit dem erwarteten Namen und Typ hat oder erbt.
Lassen Sie parameterlose Konstruktoren und Mitgliedsinitialisierer in Strukturen zu.
Siehe parameterlose Struct-Konstruktoren Spezifikation.
Offene Fragen
- Wie kann man Datensatz-Structs in Metadaten erkennen? (wir haben keine unaussprechliche Klon-Methode, die wir nutzen könnten...)
Beantwortet
- bestätigen Sie, dass das PrintMembers-Design beibehalten werden soll (separate Methode, die
bool
zurückgibt) (Antwort: Ja) - prüfen, ob wir
record ref struct
zulassen werden (Problem mitIEquatable<RefStruct>
und ref-Feldern) (Antwort: ja) - prüfen, ob die Implementierung von Equality-Mitgliedern möglich ist. Die Alternative ist, dass die synthetisierten
bool Equals(R other)
,bool Equals(object? other)
und Operatoren einfach anValueType.Equals
delegiert werden. (Antwort: Ja) - prüfen, ob wir Feldinitialisierungen zulassen wollen, wenn es einen primären Konstruktor gibt. Möchten wir auch parameterlose Strukturkonstruktoren zulassen, während wir daran arbeiten (das Aktivierungsproblem wurde anscheinend behoben)? (Antwort: Ja, die aktualisierte Spezifikation sollte in LDM überprüft werden)
- Wie viel möchten wir über die
Combine
-Methode sagen? (Antwort: so wenig wie möglich) - sollten wir einen benutzerdefinierten Konstruktor mit einer Kopierkonstruktor-Signatur verbieten? (Antwort: Nein, in der Datensatz-Structs-Spezifikation gibt es keinen Begriff für einen Kopierkonstruktor)
- bestätigen Sie, dass wir Mitglieder mit dem Namen "Clone" nicht zulassen möchten. (Antwort: richtig)
- Überprüfen Sie, dass die synthetische
Equals
-Logik funktional äquivalent zur Laufzeitimplementierung ist (z.B. float.NaN) (Antwort: bestätigt in LDM). - können Attribute, die auf Felder oder Eigenschaften abzielen, in die Liste der Positionsparameter aufgenommen werden? (Antwort: ja, wie bei der Datensatz-Klasse)
-
with
bei Generics? (Antwort: außerhalb des Bereichs von C# 10) - sollte
GetHashCode
einen Hash des Typs selbst enthalten, um unterschiedliche Werte zwischenrecord struct S1;
undrecord struct S2;
zu erhalten? (Antwort: nein)
C# feature specifications