Datensätze (C#-Referenz)
Sie verwenden den record
-Modifizierer, um einen Verweistyp zu definieren, der integrierte Funktionalität zum Kapseln von Daten bereitstellt. C# 10 ermöglicht die record class
-Syntax als Synonym, um einen Verweistyp zu verdeutlichen und record struct
, um einen Werttyp mit ähnlicher Funktionalität zu definieren.
Wenn Sie einen primären Konstruktor für einen Datensatz deklarieren, generiert der Compiler öffentliche Eigenschaften für die Parameter des primären Konstruktors. Die Parameter des primären Konstruktors für einen Datensatz werden als Positionsparameter bezeichnet. Der Compiler erstellt Positionseigenschaften, die den primären Konstruktor oder Positionsparameter spiegeln. Der Compiler synthetisiert keine Eigenschaften für Parameter primärer Konstruktoren für Typen, die nicht über den record
-Modifizierer verfügen.
Die folgenden beiden Beispiele veranschaulichen record
- (oder record class
-) Verweistypen:
public record Person(string FirstName, string LastName);
public record Person
{
public required string FirstName { get; init; }
public required string LastName { get; init; }
};
Die folgenden beiden Beispiele veranschaulichen record struct
-Werttypen:
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; }
}
Sie können auch Datensätze mit änderbaren Eigenschaften und Feldern erstellen:
public record Person
{
public required string FirstName { get; set; }
public required string LastName { get; set; }
};
Datensatzstrukturen können ebenfalls veränderlich sein, sowohl Positionsdatensatzstrukturen als auch Datensatzstrukturen ohne Positionsparameter:
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; }
}
Datensätze können zwar änderbar sein, sind jedoch primär dafür vorgesehen, unveränderliche Datenmodelle zu unterstützen. Der Datensatztyp bietet die folgenden Funktionen:
- Präzise Syntax zum Erstellen eines Verweistyps mit unveränderlichen Eigenschaften
- Integriertes Verhalten, das für einen datenzentrierten Verweistyp nützlich ist:
- Unterstützung für Vererbungshierarchien
Die Beispiele oben zeigen einige Unterschiede zwischen Datensätzen, die Verweistypen sind, und Datensätzen, die Werttypen sind:
- Das
record
- oderrecord class
-Element deklariert einen Verweistyp. Dasclass
-Schlüsselwort ist optional, kann aber für Leser mehr Klarheit schaffen. Dasrecord struct
-Element deklariert einen Werttyp. - Positionseigenschaften sind in
record class
- undreadonly record struct
-Elementen unveränderlich. Sie sind inrecord struct
-Elementen veränderbar.
Im weiteren Verlauf dieses Artikels werden sowohl record class
- als auch record struct
-Typen erläutert. Die Unterschiede werden in den einzelnen Abschnitten ausführlich beschrieben. Sie sollten sich zwischen record class
- und record struct
-Elementen entscheiden, ähnlich wie bei der Entscheidung zwischen class
- und struct
-Elementen. Der Begriff Datensatz wird verwendet, um Verhalten zu beschreiben, das für alle Datensatztypen gilt. record struct
oder record class
wird verwendet, um das Verhalten zu beschreiben, das nur für Struktur- bzw. Klassentypen gilt. Der record struct
-Typ wurde in C# 10 eingeführt.
Positionssyntax für die Eigenschaftendefinition
Sie können Positionsparameter verwenden, um die Eigenschaften eines Datensatzes zu deklarieren und die Eigenschaftswerte zu initialisieren, wenn Sie eine Instanz erstellen:
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 }
}
Wenn Sie die Positionssyntax für die Eigenschaftendefinition verwenden, erstellt der Compiler Folgendes:
- Eine öffentliche, automatisch implementierte Eigenschaft für jeden Positionsparameter, der in der Datensatzdeklaration bereitgestellt wird.
- Für - und -Typen handelt es sich um eine
record
init-onlyreadonly record struct
-Eigenschaft. - Für
record struct
-Typen ist dies eine Lese-/Schreibeigenschaft.
- Für - und -Typen handelt es sich um eine
- Ein primärer Konstruktor, dessen Parameter mit den Parametern mit fester Breite der Datensatzdeklaration übereinstimmen
- Bei Datensatzstrukturtypen ein parameterloser Konstruktor, der jedes Feld auf seinen Standardwert festlegt.
- Eine
Deconstruct
-Methode mit einemout
-Parameter für jeden Positionsparameter, der in der Datensatzdeklaration bereitgestellt wird. Die Methode dekonstruiert Eigenschaften, die mithilfe der Positionssyntax definiert wurden. Es werden Eigenschaften ignoriert, die mithilfe der Standardeigenschaftensyntax definiert werden.
Sie sollten einem dieser Elemente, die der Compiler aus der Datensatzdefinition erstellt, Attribute hinzufügen. Sie können einem beliebigen Attribut, das Sie auf die Eigenschaften des Datensatzes mit Feldern fester Breite anwenden, ein Ziel hinzufügen. Im folgenden Beispiel wird System.Text.Json.Serialization.JsonPropertyNameAttribute auf jede Eigenschaft des Person
-Datensatzes angewendet. Das property:
-Ziel gibt an, dass das Attribut auf die vom Compiler generierte Eigenschaft angewendet wird. Andere Werte sind beispielsweise field:
zum Anwenden des Attributs auf das Feld und param:
zum Anwenden des Attributs auf den Parameter.
/// <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);
Das vorherige Beispiel zeigt auch, wie XML-Dokumentationskommentare für den Datensatz erstellt werden. Sie können das <param>
-Tag hinzufügen, um die Dokumentation für die Parameter des primären Konstruktors hinzuzufügen.
Wenn die generierte, automatisch implementierte Eigenschaftendefinition nicht die gewünschte Eigenschaft ist, können Sie eine eigene Eigenschaft mit demselben Namen definieren. Beispielsweise können Sie die Barrierefreiheit oder Änderbarkeit bearbeiten oder eine Implementierung für den get
- oder set
-Accessor bereitstellen. Wenn Sie die Eigenschaft in Ihrer Quelle deklarieren, müssen Sie sie über den Positionsparameter des Datensatzes initialisieren. Wenn ihre Eigenschaft eine automatisch implementierte Eigenschaft ist, müssen Sie die Eigenschaft initialisieren. Wenn Sie Ihrer Quelle ein Unterstützungsfeld hinzufügen, müssen Sie das Unterstützungsfeld initialisieren. Der generierte Dekonstruktor verwendet Ihre Eigenschaftendefinition. Im folgenden Beispiel werden beispielsweise die FirstName
- und LastName
-Eigenschaften eines Positionsdatensatzes public
deklariert, der Positionsparameter Id
wird jedoch auf internal
beschränkt. Sie können diese Syntax für Datensätze und Datensatzstrukturtypen verwenden.
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
}
Ein Datensatztyp muss keine Positionseigenschaften deklarieren. Sie können einen Datensatz ohne Positionseigenschaften deklarieren, und Sie können andere Felder und Eigenschaften deklarieren, wie im folgenden Beispiel gezeigt:
public record Person(string FirstName, string LastName)
{
public string[] PhoneNumbers { get; init; } = [];
};
Wenn Sie Eigenschaften mit der Standardeigenschaftensyntax definieren, aber den Zugriffsmodifizierer weglassen, sind die Eigenschaften implizit private
.
Unveränderlichkeit
Ein Positionsdatensatz und eine schreibgeschützte Positionsdatensatzstruktur deklarieren init-only-Eigenschaften. Eine Positionsdatensatzstruktur deklariert Lese-/Schreibeigenschaften. Sie können diese Standardwerte überschreiben, wie im vorherigen Abschnitt gezeigt.
Die Unveränderlichkeit kann hilfreich sein, wenn Sie einen threadsicheren datenzentrierten Typ benötigen oder davon abhängig sind, dass ein Hashcode in einer Hashtabelle unverändert bleibt. Die Unveränderlichkeit ist jedoch nicht für alle Datenszenarios geeignet. Entity Framework Core unterstützt beispielsweise keine Updates mit unveränderlichen Entitätstypen.
Unabhängig davon, ob init-only-Eigenschaften aus Positionsparametern (record class
oder readonly record struct
) oder durch Angabe von init
-Zugriffsmethoden erstellt wurden, weisen sie eine flache Unveränderlichkeit auf. Nach der Initialisierung können Sie den Wert der Werttypeigenschaften oder den Verweis der Verweistypeigenschaften nicht ändern. Allerdings können die Daten, auf die eine Verweistypeigenschaft verweist, geändert werden. Das folgende Beispiel zeigt, dass der Inhalt einer unveränderlichen Verweistypeigenschaft (in diesem Fall ein Array) änderbar ist:
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
}
Die für Datensatztypen eindeutigen Features werden von durch den Compiler synthetisierte Methoden implementiert, und keine dieser Methoden beeinträchtigt die Unveränderlichkeit durch Ändern des Objektzustands. Sofern nicht angegeben, werden die synthetisierten Methoden für record
-, record struct
- und readonly record struct
-Deklarationen generiert.
Wertgleichheit
Wenn Sie Gleichheitsmethoden nicht überschreiben oder ersetzen, bestimmt der von Ihnen deklarierte Typ, wie Gleichheit definiert wird:
- Bei
class
-Typen sind zwei Objekte gleich, wenn sie auf das gleiche Objekt im Arbeitsspeicher verweisen. - Bei
struct
-Typen sind zwei Objekte gleich, wenn sie denselben Typ aufweisen und die gleichen Werte speichern. - Für Typen mit dem
record
-Modifizierer (record class
,record struct
undreadonly record struct
) sind zwei Objekte gleich, wenn sie denselben Typ aufweisen und die gleichen Werte speichern.
Die Definition von Gleichheit für record struct
entspricht der Definition für struct
. Der Unterschied besteht darin, dass sich die Implementierung für struct
in ValueType.Equals(Object) befindet und auf Reflexion basiert. Bei Datensätzen wird die Implementierung vom Compiler synthetisiert, und es werden deklarierte Datenmember verwendet.
Verweisgleichheit ist für einige Datenmodelle erforderlich. Entity Framework Core hängt beispielsweise von der Verweisgleichheit ab, um sicherzustellen, dass für konzeptionell eine Entität nur eine Instanz eines Entitätstyps verwendet wird. Aus diesem Grund sind Datensätze und Datensatzstrukturen für die Verwendung als Entitätstypen in Entity Framework Core nicht geeignet.
Im folgenden Beispiel wird die Wertgleichheit von Datensatztypen veranschaulicht:
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
}
Zum Implementieren von Wertgleichheit synthetisiert der Compiler mehrere Methoden, inklusive:
Eine Überschreibung von Object.Equals(Object) Es ist ein Fehler, wenn die Außerkraftsetzung explizit deklariert wird.
Diese Methode wird als Grundlage für die statische Object.Equals(Object, Object)-Methode verwendet, wenn beide Parameter nicht NULL sind.
Ein
virtual
odersealed
,Equals(R? other)
, wobeiR
der Datensatztyp ist. Diese Methode implementiert IEquatable<T>. Diese Methode kann explizit deklariert werden.Wenn der Datensatztyp von einem Basisdatensatztyp
Base
abgeleitet wird,Equals(Base? other)
. Es ist ein Fehler, wenn die Außerkraftsetzung explizit deklariert wird. Wenn Sie Ihre eigene Implementierung vonEquals(R? other)
bereitstellen, stellen Sie auch eine Implementierung vonGetHashCode
bereit.Eine Überschreibung von Object.GetHashCode() Diese Methode kann explizit deklariert werden.
Überschreibungen der Operatoren
==
und!=
. Es ist ein Fehler, wenn die Operatoren explizit deklariert werden.Wenn der Datensatztyp von einem Basisdatensatztyp abgeleitet wird,
protected override Type EqualityContract { get; };
. Diese Eigenschaft kann explizit deklariert werden. Weitere Informationen finden Sie unter Gleichheit in Vererbungshierarchien.
Wenn ein Datensatztyp über eine Methode verfügt, die mit der Signatur einer synthetisierten Methode übereinstimmt, die explizit deklariert werden kann, wird diese Methode vom Compiler nicht synthetisiert.
Nichtdestruktive Mutation
Wenn eine Instanz mit einigen Änderungen kopieren müssen, können Sie einen with
-Ausdruck verwenden, um eine nichtdestruktive Mutation zu erzielen. Ein with
-Ausdruck erstellt eine neue Datensatzinstanz, bei der es sich um eine Kopie einer vorhandenen Datensatzinstanz handelt, bei der bestimmte Eigenschaften und Felder geändert wurden. Mit der Objektinitialisierersyntax können Sie die zu ändernden Werte angeben, wie im folgenden Beispiel gezeigt:
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
}
Der with
-Ausdruck kann Positionseigenschaften festlegen oder Eigenschaften, die mithilfe der Standardeigenschaftensyntax erstellt werden. Explizit deklarierte Eigenschaften müssen eine init
- oder set
-Zugriffsmethode aufweisen, damit sie in einem with
-Ausdruck geändert werden können.
Das Ergebnis eines with
-Ausdrucks ist eine flache Kopie. Dies bedeutet, dass bei einer Verweiseigenschaft nur der Verweis auf eine Instanz kopiert wird. Sowohl der ursprüngliche Datensatz als auch die Kopie verfügen über einen Verweis auf dieselbe Instanz.
Um dieses Feature für record class
-Typen zu implementieren, synthetisiert der Compiler eine Klonmethode und einen Kopierkonstruktor. Die virtuelle Klonmethode gibt einen neuen Datensatz zurück, der vom Kopierkonstruktor initialisiert wurde. Wenn Sie einen with
-Ausdruck verwenden, erstellt der Compiler Code, der die Klonmethode aufruft, und legt dann die Eigenschaften fest, die im with
-Ausdruck angegeben werden.
Wenn Sie ein anderes Kopierverhalten benötigen, können Sie einen eigenen Kopierkonstruktor in record class
schreiben. Wenn Sie so vorgehen, synthetisiert der Compiler keinen Kopierkonstruktor. Legen Sie den Konstruktor als private
fest, wenn der Datensatz sealed
ist, und legen Sie ihn andernfalls als protected
fest. Der Compiler synthetisiert keinen Kopierkonstruktor für record struct
-Typen. Sie können einen solchen schreiben, aber der Compiler generiert keine entsprechenden Aufrufe für with
-Ausdrücke. Die Werte von record struct
werden bei Zuweisung kopiert.
Sie können die Klonmethode nicht überschreiben, und Sie können keinen Member mit dem Namen Clone
in einem Datensatztyp erstellen. Der tatsächliche Name der Klonmethode wird vom Compiler generiert.
Integrierte Formatierung für die Anzeige
Datensatztypen verfügen über eine vom Compiler generierte ToString-Methode, die die Namen und Werte der öffentlichen Eigenschaften und Felder anzeigt. Die ToString
-Methode gibt eine Zeichenfolge in folgendem Format zurück:
<Datensatztypname> { <Eigenschaftsname> = <Wert>, <Eigenschaftsname> = <Wert>, ...}
Die für <value>
ausgegebene Zeichenfolge ist die Zeichenfolge, die von ToString() für den Typ der Eigenschaft zurückgegeben wird. Im folgenden Beispiel ist ChildNames
ein System.Array, wobei ToString
System.String[]
zurückgibt:
Person { FirstName = Nancy, LastName = Davolio, ChildNames = System.String[] }
Um dieses Feature zu implementieren, synthetisiert der Compiler in record class
-Typen eine virtuelle PrintMembers
-Methode und eine ToString-Überschreibung. In record struct
-Typen ist dieser Member private
.
Mit der ToString
-Überschreibung wird ein StringBuilder-Objekt mit dem Typnamen gefolgt von einer öffnenden eckigen Klammer erstellt. Es ruft PrintMembers
auf, um Eigenschaftennamen und Werte hinzuzufügen, und fügt dann die schließende Klammer hinzu. Das folgende Beispiel zeigt Code, der mit dem Inhalt der synthetisierten Überschreibung vergleichbar ist:
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();
}
Sie können Ihre eigene Implementierung von PrintMembers
oder der ToString
-Überschreibung bereitstellen. Beispiele finden Sie weiter unten in diesem Artikel im Abschnitt PrintMembers
-Formatierung in abgeleiteten Datensätzen. In C# 10 und höheren Versionen kann Ihre Implementierung von ToString
den sealed
-Modifizierer enthalten, der den Compiler daran hindert, eine ToString
-Implementierung für abgeleitete Datensätze zu synthetisieren. Sie können eine konsistente Zeichenfolgendarstellung in einer Hierarchie von record
-Typen erstellen. (Abgeleitete Datensätze umfassen weiterhin eine PrintMembers
-Methode, die für alle abgeleiteten Eigenschaften generiert wird.)
Vererbung
Dieser Abschnitt gilt nur für record class
-Typen.
Ein Datensatz kann von einem anderen Datensatz erben. Ein Datensatz kann jedoch nicht von einer Klasse erben, und eine Klasse kann nicht von einem Datensatz erben.
Positionsparameter in abgeleiteten Datensatztypen
Der abgeleitete Datensatz deklariert Positionsparameter für alle Parameter im primären Konstruktor des Basisdatensatzes. Der Basisdatensatz deklariert und initialisiert diese Eigenschaften. Der abgeleitete Datensatz blendet diese nicht aus. Stattdessen erstellt und initialisiert er nur Eigenschaften für Parameter, die nicht in seinem Basisdatensatz deklariert sind.
Im folgenden Beispiel wird die Vererbung mit Positionseigenschaftensyntax veranschaulicht:
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 }
}
Gleichheit in Vererbungshierarchien
Dieser Abschnitt gilt für record class
-Typen, aber nicht für record struct
-Typen. Damit zwei Datensatzvariablen gleich sind, muss der Laufzeittyp gleich sein. Die Typen der enthaltenden Variablen können unterschiedlich sein. Der Vergleich geerbter Gleichheit wird im folgenden Codebeispiel veranschaulicht:
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
}
Im Beispiel werden alle Variablen als Person
deklariert, auch wenn die Instanz ein abgeleiteter Typ von Student
oder Teacher
ist. Die Instanzen verfügen alle über dieselben Eigenschaften und Eigenschaftswerte. student == teacher
gibt jedoch False
zurück, obwohl beide Person
-Typvariablen sind, und student == student2
gibt True
zurück, obwohl die eine eine Person
-Variable und die andere eine Student
-Variable ist. Der Gleichheitstest hängt vom Laufzeittyp des tatsächlichen Objekts ab, nicht vom deklarierten Typ der Variablen.
Um dieses Verhalten zu implementieren, synthetisiert der Compiler eine EqualityContract
-Eigenschaft, die ein Type-Objekt zurückgibt, das mit dem Typ des Datensatzes übereinstimmt. Das EqualityContract
-Element ermöglicht den Gleichheitsmethoden, den Laufzeittyp von Objekten bei der Überprüfung auf Gleichheit zu vergleichen. Wenn der Basistyp eines Datensatzes object
ist, ist diese Eigenschaft virtual
. Wenn der Basistyp einem anderen Datensatztyp entspricht, ist diese Eigenschaft eine Überschreibung. Wenn der Datensatztyp sealed
lautet, ist diese Eigenschaft effektiv versiegelt (sealed
), da der Typ sealed
ist.
Wenn Code zwei Instanzen eines abgeleiteten Typs vergleicht, überprüfen die synthetisierten Gleichheitsmethoden alle Datenmember der Basis- und abgeleiteten Typen auf Gleichheit. Die synthetisierte GetHashCode
-Methode verwendet die GetHashCode
-Methode aus allen im Basistyp und im abgeleiteten deklarierten Datenmembern. Die Datenmember eines record
-Elements umfassen alle deklarierten Felder und das vom Compiler synthetisierte Sicherungsfeld für alle automatisch implementierten Eigenschaften.
with
-Ausdrücke in abgeleiteten Datensätzen
Das Ergebnis eines with
-Ausdrucks weist denselben Laufzeittyp auf wie der Operand des Ausdrucks. Alle Eigenschaften des Laufzeittyps werden kopiert, Sie können jedoch nur Eigenschaften des Kompilierzeittyps festlegen, wie im folgenden Beispiel gezeigt:
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
-Formatierung in abgeleiteten Datensätzen
Die synthetisierte PrintMembers
-Methode eines abgeleiteten Datensatztyps ruft die Basisimplementierung auf. Das Ergebnis ist, dass alle öffentlichen Eigenschaften und Felder sowohl von abgeleiteten als auch Basistypen in der ToString
-Ausgabe enthalten sind, wie im folgenden Beispiel gezeigt:
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 }
}
Sie können Ihre eigene Implementierung der PrintMembers
-Methode bereitstellen. Wenn Sie dies tun, verwenden Sie die folgende Signatur:
- Für einen
sealed
-Datensatz, der vonobject
abgeleitet wird (deklariert keinen Basisdatensatz):private bool PrintMembers(StringBuilder builder)
; - Für einen
sealed
-Datensatz, der von einem anderen Datensatz abgeleitet ist (beachten Sie, dass der einschließende Typsealed
ist, so dass die Methode effektivsealed
lautet):protected override bool PrintMembers(StringBuilder builder)
; - Für einen Datensatz, der nicht
sealed
ist und von einem Objekt abgeleitet wird:protected virtual bool PrintMembers(StringBuilder builder);
- Für einen Datensatz, der nicht
sealed
ist und von einem anderen Datensatz abgeleitet wird:protected override bool PrintMembers(StringBuilder builder);
Im Folgenden finden Sie ein Beispiel für Code, der die synthetisierten PrintMembers
-Methoden ersetzt, ein Beispiel für einen Datensatztyp, der vom Objekt abgeleitet wird, und ein Beispiel für einen Datensatztyp, der von einem anderen Datensatz abgeleitet wird:
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 }
}
Hinweis
In C# 10 und höheren Versionen synthetisiert der Compiler PrintMembers
in abgeleiteten Datensätzen selbst dann, wenn ein Basisdatensatz die ToString
-Methode versiegelt hat. Sie können auch eine eigene Implementierung von PrintMembers
erstellen.
Dekonstruktorverhalten in abgeleiteten Datensätzen
Die Deconstruct
-Methode eines abgeleiteten Datensatzes gibt die Werte aller Positionseigenschaften des Kompilierzeittyps zurück. Wenn der Variablentyp ein Basisdatensatz ist, werden nur die Basisdatensatzeigenschaften dekonstruiert, es sei denn, das Objekt wird in den abgeleiteten Typ umgewandelt. Das folgende Beispiel zeigt, wie ein Dekonstruktor für einen abgeleiteten Datensatz aufgerufen wird.
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
}
Generische Einschränkungen
Das Schlüsselwort record
ist ein Modifizierer für einen class
- oder struct
-Typ. Das Hinzufügen des record
-Modifizierers schließt das weiter oben in diesem Artikel beschriebene Verhalten ein. Es gibt keine generische Einschränkung, die erfordert, dass ein Typ ein Datensatz ist. Eine record class
erfüllt die class
-Einschränkung. Eine record struct
erfüllt die struct
-Einschränkung. Weitere Informationen finden Sie unter Einschränkungen für Typparameter (C#-Programmierhandbuch).
C#-Sprachspezifikation
Weitere Informationen finden Sie im Abschnitt Klassen der C#-Sprachspezifikation.
Weitere Informationen zu diesen Features finden Sie in den folgenden Featurevorschlägen:
Weitere Informationen
Feedback
https://aka.ms/ContentUserFeedback.
Bald verfügbar: Im Laufe des Jahres 2024 werden wir GitHub-Tickets als Feedbackmechanismus für Inhalte auslaufen lassen und es durch ein neues Feedbacksystem ersetzen. Weitere Informationen finden Sie unter:Einreichen und Feedback anzeigen für