CodeGym /Kurse /C# SELF /Einführung in record ...

Einführung in record und Positionssyntax

C# SELF
Level 19, Lektion 1
Verfügbar

1. Das Problem der Datenübergabe

Angenommen, wir haben eine App für eine Schule und müssen Infos über einen Schüler zwischen verschiedenen Modulen weitergeben: Name, Geburtsjahr, Klasse. Wie machen wir das normalerweise?

Wir erstellen eine Klasse wie:


public class Student
{
    public string Name { get; set; }
    public int YearOfBirth { get; set; }
    public string Class { get; set; }
}
Klasse zum Speichern von Schülerdaten (alter Ansatz)

Sieht vertraut aus. Aber dieser Ansatz hat ein paar Probleme:

  • Wenn du zwei Studenten auf Gleichheit prüfst, wird standardmäßig nur die Referenz verglichen. Also: Zwei Studenten mit gleichen Feldern, aber unterschiedlichen Objekten — sind nicht gleich!
  • Die Klasse kann nach der Erstellung geändert werden, was manchmal zu Fehlern führt (vor allem, wenn das Objekt schon irgendwo verwendet wird);
  • Viel „Boilerplate“-Code: Konstruktoren, Vergleichsmethoden, Kopiermethoden (Clones), ToString.

Und wie du dir wahrscheinlich schon gedacht hast: C# kann uns diese Sorgen abnehmen! Hier kommt — record.

2. Was ist ein record?

record ist ein spezieller Typ in C#, der extra fürs Speichern von Daten gemacht wurde. Ein record hat zwei Hauptfeatures:

  1. Immutabilität (Unveränderlichkeit): Objekte vom Typ record sind standardmäßig unveränderlich, das heißt, ihre Properties werden einmal beim Erstellen gesetzt und können danach nicht mehr geändert werden (genauer gesagt: set-Setter sind privat). Man kann auch veränderliche records machen, aber standardmäßig ist alles immutable.
  2. Wertvergleich: Wenn zwei record-Objekte in allen Feldern die gleichen Werte haben, gelten sie als gleich (== und .Equals() funktionieren anders!).

Eigentlich ist record die perfekte Hülle, um Daten zwischen App-Schichten zu übergeben (z.B. von der DB zum Controller, vom Controller zum View usw.).

3. Syntax von record

Der einfachste Weg — Positionssyntax

Wenn wir einfach einen Satz Werte übergeben wollen, deklarieren wir den Typ mit einer kurzen Zeile:


public record Student(string Name, int YearOfBirth, string Class);
Positionssyntax für record

Was passiert im Hintergrund? Der Compiler generiert für uns:

  • Automatische Properties nur zum Lesen (mit privatem Setter);
  • Einen Konstruktor, der alle Parameter nimmt;
  • Vergleichs- und Kopiermethoden;
  • Ein cooles ToString, das die Ausgabe schön formatiert!

Verwendung von positional record

Probieren wir diesen neuen Typ mal in unserer Schul-App aus:

var student1 = new Student("Ivan", 2008, "8A");
var student2 = new Student("Maria", 2008, "8B");

Auf die Properties greifst du wie gewohnt zu (nur ändern geht nicht):

Console.WriteLine($"{student1.Name}, {student1.YearOfBirth}, {student1.Class}");

Versuch, ein Property nach dem Erstellen zu ändern

student1.Name = "Petr"; // Fehler! Property ist read-only.

Wenn du diese Zeile auskommentierst — meckert der Compiler sofort: Wert kann nicht für ein read-only Property gesetzt werden.

So sieht das automatisch generierte ToString aus

Console.WriteLine(student1); // Gibt aus: Student { Name = Ivan, YearOfBirth = 2008, Class = 8A }

Alles schön und verständlich, auch ohne manuelles Formatieren!

4. Vergleich von record-Objekten

Zur Erinnerung: Wenn du zwei verschiedene Objekte einer klassischen Klasse mit gleichen Daten erstellst, sind sie trotzdem nicht gleich:

var a = new Student("Ivan", 2008, "8A");
var b = new Student("Ivan", 2008, "8A");

Console.WriteLine(a == b); // Für Klasse: false

Wenn Student aber ein record ist, funktioniert Gleichheit so, wie du es erwartest:


public record Student(string Name, int YearOfBirth, string Class);

var a = new Student("Ivan", 2008, "8A");
var b = new Student("Ivan", 2008, "8A");

Console.WriteLine(a == b); // Für record: true!
Vergleich von record-Objekten nach Wert

Das heißt: Zwei records mit gleichen Feldern sind gleich, auch wenn es zwei verschiedene Objekte im Speicher sind.

5. Wie sieht das intern aus?

Studierende sind oft überrascht, wie viel der Compiler bei records für uns erledigt. Lass uns mal vergleichen, wie viel Code du für eine Klasse schreiben müsstest und was ein record macht.

Die gute alte Klasse von Hand geschrieben

public class Student
{
    public string Name { get; }
    public int YearOfBirth { get; }
    public string Class { get; }

    public Student(string name, int yearOfBirth, string @class)
    {
        Name = name;
        YearOfBirth = yearOfBirth;
        Class = @class; //Verweis auf die eigene Klasse
    }

    public override bool Equals(object? obj)
    {
        if (obj is not Student other) return false;
        return Name == other.Name && YearOfBirth == other.YearOfBirth && Class == other.Class;
    }

    public override int GetHashCode()
    {
        return HashCode.Combine(Name, YearOfBirth, Class);
    }

    public override string ToString()
    {
        return $"Student {{ Name = {Name}, YearOfBirth = {YearOfBirth}, Class = {Class} }}";
    }
}

Kein Wunder, dass Programmierer paranoid werden — wir schreiben das Gleiche zehnmal!

Record — eine Zeile


public record Student(string Name, int YearOfBirth, string Class);
Record — das Gleiche, aber in einer Zeile!

6. Record und Immutabilität: Was geht, was nicht?

Bei records sind Properties standardmäßig read-only, und das verhindert viele Bugs. Aber wenn du unbedingt willst (z.B. für ein sehr altes API), kannst du auch veränderliche records machen:

public record MutableStudent
{
    public string Name { get; set; }
    public int YearOfBirth { get; set; }
    public string Class { get; set; }
}

Jetzt kannst du die Felder ändern, aber du verlierst einige Vorteile (z.B. Sicherheit).

7. Destrukturierung von record

Da die Positionssyntax sehr wie bei Tuples ist, kannst du ein record easy destrukturieren:


var student = new Student("Ivan", 2008, "8A");

var (name, year, className) = student;

Console.WriteLine($"{name} - {year}, {className}"); // Ivan - 2008, 8A
Destrukturierung eines positional record

Der Compiler generiert für jedes positional record eine Deconstruct-Methode, was die Arbeit mit LINQ, switch-patterns usw. viel angenehmer macht.

8. Record — Klasse oder struct?

Standardmäßig ist record ein Referenztyp, wie eine Klasse. Also gelten alle Besonderheiten von Referenztypen (Heap, Referenzkopie usw.).

Wenn du einen Werttyp willst (value type), gibt’s in C# auch dafür was — du kannst einfach schreiben:


public record struct Point(int X, int Y);
Record struct — Werttyp-Record

Aber zum Übergeben von Daten nimmt man fast immer die klassische Form von records, also Referenztyp. Mehr zu record struct gibt’s in den nächsten Vorlesungen :P

Vergleich class, struct, record

Typ Immutabilität standardmäßig Wertvergleich Einfache Destrukturierung Auto ToString
class Nein Nein (per Referenz) Nein Nein
struct Nein Ja Nein Nein
record Ja Ja Ja Ja

9. Besonderheiten und typische Fehler

Viele Anfänger verwechseln die Feinheiten bei records. Zum Beispiel erwarten manche, dass das Ändern eines Feldes bei einem record-Objekt auch ein anderes ändert (wie bei Klassen mit Referenzkopie). Nope! Außerdem: Wenn du with schreibst, denk dran, dass immer eine Kopie erstellt wird, nicht das Original geändert. Records sind perfekt für Logik, wo deine App auf Datenkonsistenz und Vorhersagbarkeit achtet.

Ja, wenn du Felder mit init statt set deklarierst, kannst du sie auch nur beim Erstellen oder mit with setzen, aber nicht später.

2
Aufgabe
C# SELF, Level 19, Lektion 1
Gesperrt
Vergleich von record nach Wert
Vergleich von record nach Wert
2
Aufgabe
C# SELF, Level 19, Lektion 1
Gesperrt
Destrukturierung eines record
Destrukturierung eines record
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION