CodeGym /Kurse /C# SELF /Unveränderlichkeit und with<...

Unveränderlichkeit und with-Ausdrücke

C# SELF
Level 19 , Lektion 2
Verfügbar

1. Was ist Unveränderlichkeit?

Lass uns mit einer Analogie starten: Stell dir vor, du bist Buchhalter und erstellst jeden Tag einen Verkaufsbericht. Als echter Profi änderst du nicht den Bericht der letzten Woche, sondern erstellst einen neuen – basierend auf dem alten, aber mit aktualisierten Daten. Genau so ist es mit unveränderlichen Objekten in der Programmierung: Nach der Erstellung ändert sich so ein Objekt nicht mehr, und jede "Änderung" bedeutet, dass eine neue Kopie erzeugt wird.

Unveränderlichkeit (immutability) ist die Eigenschaft eines Objekts, sich nach der Initialisierung nicht mehr zu ändern. Alle seine Properties sind dann "eingefroren": Wenn du einen anderen Wert willst – erstelle ein neues Objekt.

Wozu braucht man das?

  • Sorgt für Sicherheit: Wenn niemand dein Objekt aus Versehen ändern kann, werden deine Daten nicht plötzlich kaputt gemacht. Das passiert oft in Multithreading-Programmen, wenn mehrere Threads gleichzeitig was ändern wollen.
  • Debuggen wird einfacher: Wenn sich ein Objekt nicht ändert, weißt du genau, was nach der Erstellung passiert ist.
  • Praktisch für die Datenübergabe, besonders in verteilten Systemen, wo Kopien auseinanderlaufen können.
  • Ermöglicht "Snapshots" (snapshots) – die Änderungshistorie wird klar sichtbar.

2. Unveränderlichkeit bei record-Typen

Wenn du einen normalen class deklarierst, sind seine Properties standardmäßig veränderbar (mutable). Beispiel:

public class UserProfile
{
    public string Name { get; set; }
    public int Age { get; set; }
}

var user = new UserProfile { Name = "Ivan", Age = 25 };
user.Age = 26; // Alles ok – normale Klasse: Alter wird "on the fly" geändert

Bei record sieht es anders aus: Sie sind zum Speichern von unveränderlichen Daten gedacht.


public record UserProfile(string Name, int Age);

// Objekt erstellen:
var user = new UserProfile("Ivan", 25);

// Versuch, das Alter zu ändern:
user.Age = 26; // Compilerfehler: Property ist read-only!
record – Properties sind nur lesbar (init-only)

Properties eines positional record sind nur lesbar (init-only). Du kannst sie nach der Erstellung nicht ändern, aber du kannst das with-Ausdruck nutzen, um eine neue Kopie mit geändertem Property zu erstellen.

Veränderbare Klassen vs. unveränderliche records

Klasse Record-positional
Properties standardmäßig
get; set;
Unveränderlich
get; init;
Wie ändern
Zugriff auf Property
Nur durch Erstellen einer neuen Kopie
Objektvergleich
Nach Referenz (ReferenceEquals)
Nach Wert (Equals)
Praktisch für Datenübergabe
Nicht immer
Ja

3. with-Ausdrücke

Du denkst jetzt vielleicht – "record ist cool, aber wie lebt man damit, wenn man sie nicht ändern kann?" Hier kommt die Magie der with-Ausdrücke!

with ist spezieller Syntax, mit dem du eine neue Kopie eines records erstellen kannst, wobei du nur die gewünschten Properties änderst.
Also: "Nimm dieses Objekt, mach eine Kopie, aber ändere hier ein paar Properties."

Einfachstes Beispiel


var user1 = new UserProfile("Anna", 30);
// ... aber das Leben bleibt nicht stehen, und Anna ist älter geworden
var user2 = user1 with { Age = 31 };
// user1 bleibt gleich, user2 ist eine Kopie, aber ein Jahr älter

Console.WriteLine(user1); // UserProfile { Name = Anna, Age = 30 }
Console.WriteLine(user2); // UserProfile { Name = Anna, Age = 31 }

Was passiert unter der Haube?

Das ist kein Mutant-Klon, sondern ein neues Objekt, das mit einer automatisch generierten Clone()-Methode erstellt wird, die eine Kopie macht und neue Werte einsetzt.

Wenn es with-Ausdrücke im echten Leben gäbe, könntest du morgens nicht im "alten müden Körper" aufstehen, sondern in einer Kopie von dir mit besserer Laune und größeren Muskeln (aber nur, wenn du ein record wärst).

4. Ein bisschen über Verschachtelung und Kopieren

Wenn ein record andere records enthält – alles easy:

public record Address(string City, string Street);
public record Student(string Name, int Age, string Email, Address Home);

var a1 = new Address("Moskau", "Tverskaya");
var s1 = new Student("Lena", 21, "lena@mail.ru", a1);

var s2 = s1 with { Home = a1 with { Street = "Arbat" } };

Hier funktioniert alles wirklich immutable, weil das verschachtelte Address auch ein record ist.

5. Letzte Feinheiten

Positional records = kompakt

Du kannst records in der "kurzen" Form (positional Syntax) deklarieren. Dann bekommen alle Properties automatisch init-only.

public record Course(string Name, int Credits);

var c1 = new Course("C#", 5);
var c2 = c1 with { Credits = 6 };

Analogie zu read-only Properties (init-only)

In records kannst du Properties auch explizit so deklarieren:

public record Student
{
    public string Name { get; init; }
    public int Age { get; init; }
}

Solche Properties kannst du auch nur bei der Initialisierung ändern (oder mit with).

6. Praxis: Demo-Anwendung

Lass uns unsere Lern-"Online-Schule" schreiben. Angenommen, wir haben schon ein record für einen Studenten:

public record Student(string Name, int Age, string Email);

Klassiker: Jemand hat sich bei der Adresse vertippt, aber der Student hat schon einen Account erstellt. Wie macht man ein "Update" der Email? Natürlich mit with!


var student = new Student("Ekaterina", 19, "kate@school.com");
var updatedStudent = student with { Email = "ekaterina@school.com" };

// Checken wir die Objekte:
Console.WriteLine(student);       // Student { Name = Ekaterina, Age = 19, Email = kate@school.com }
Console.WriteLine(updatedStudent); // Student { Name = Ekaterina, Age = 19, Email = ekaterina@school.com }

7. Typische Fehler und Stolperfallen

Jetzt ein bisschen Schmerz – wo machen Studis am häufigsten Fehler, wenn sie mit immutable records spielen?

  • Erstens denken viele, dass with das Originalobjekt ändert. In Wirklichkeit bleibt das Original gleich, und ein neues Objekt wird mit den geänderten Feldern erstellt. Dadurch kann man manchmal in die Falle tappen und neue Werte verlieren.
  • Zweitens: Wenn im record verschachtelte veränderbare Objekte sind (zum Beispiel ein Array oder List), dann macht das with-Ausdruck kein deep copy! Deine Collection ist für beide Kopien dieselbe.

public record Student(string Name, int Age, List<string> Subjects);

var s1 = new Student("Oleg", 22, new List<string> { "Math", "Physics" });
var s2 = s1 with { };

s1.Subjects.Add("C#"); // Oops, jetzt enthält auch s2.Subjects "C#"

Deshalb solltest du für truly immutable state nur einfache Typen oder Collections verwenden, die selbst immutable sind (ImmutableList<T> und andere aus System.Collections.Immutable).

Wenn du echte Unveränderlichkeit garantieren willst, nutze solche Collections oder mach ein manuelles deep copy.

Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION