Deep Cloning in C#: Von Reflection bis Serialisierung

Online Python-Trainer für Anfänger

Lernen Sie Python einfach ohne theoretische Überlastung. Lösen Sie praktische Aufgaben mit automatischer Überprüfung und schreiben Sie Code direkt im Browser.

Kurs starten

Tiefes Kopieren in C#: Warum ist das der Albtraum jedes Entwicklers?

\n\n

Haben Sie jemals ein Objekt in C# kopiert und dann überrascht festgestellt, dass eine Änderung in der "Kopie" das Original beschädigt? Kommt Ihnen bekannt vor? Das ist das klassische Problem der flachen Kopie (Shallow Copy). Wenn ein Objekt Referenzfelder hat (andere Klassen, Listen, Arrays), kopiert MemberwiseClone() einfach die Referenzen. Am Ende zeigen sowohl das Original als auch die Kopie auf dasselbe verschachtelte Objekt.

\n\n

In diesem Artikel werden wir uns mit Deep Cloning in C# befassen – dem Prozess, eine vollständig unabhängige Kopie eines Objekts mit allen verschachtelten Objekten zu erstellen. Wir vergleichen 5 gängige Ansätze, schreiben echten Code, führen einen gedanklichen Benchmark durch und finden heraus, welche Methode Sie für Ihr Projekt wählen sollten.

\n\n

Was ist Deep Clone und wie unterscheidet es sich von Shallow Clone?

\n\n

Stellen Sie sich vor, Sie haben eine Klasse Person, die eine Address enthält:

\n\n
public class Address\n{\n    public string City { get; set; }\n    public string Street { get; set; }\n}\n\npublic class Person\n{\n    public string Name { get; set; }\n    public int Age { get; set; }\n    public Address HomeAddress { get; set; }\n}
\n\n

Bei einem Shallow Clone (normales MemberwiseClone()) erhalten Sie eine neue Person, aber deren HomeAddress zeigt auf dasselbe Address-Objekt wie das des Originals. Ändern Sie die Straße in der Kopie – die Straße hat sich auch im Original geändert. Eine Katastrophe.

\n\n

Deep Clone löst dieses Problem: Es kopiert rekursiv alle verschachtelten Objekte und erstellt eine vollständig unabhängige Datenstruktur.

\n\n

Ansatz 1: Manuelle Implementierung über ICloneable und Kopierkonstruktoren

\n\n

Der offensichtlichste, aber auch aufwändigste Weg ist die Implementierung des ICloneable-Interfaces oder das Schreiben einer eigenen DeepClone()-Methode.

\n\n

Implementierungsbeispiel:

\n\n
public class Address : ICloneable\n{\n    public string City { get; set; }\n    public string Street { get; set; }\n\n    public object Clone()\n    {\n        return new Address { City = this.City, Street = this.Street };\n    }\n}\n\npublic class Person : ICloneable\n{\n    public string Name { get; set; }\n    public int Age { get; set; }\n    public Address HomeAddress { get; set; }\n\n    public object Clone()\n    {\n        return new Person\n        {\n            Name = this.Name,\n            Age = this.Age,\n            HomeAddress = (Address)this.HomeAddress?.Clone()\n        };\n    }\n}
\n\n

Vorteile:

\n
    \n
  • Vollständige Kontrolle über den Kopiervorgang
  • \n
  • Maximale Leistung
  • \n
  • Verständlicher Code (keine "Magie")
  • \n
\n\n

Nachteile:

\n
    \n
  • Sehr viel Boilerplate-Code
  • \n
  • Leicht zu vergessen, die Clone()-Methode beim Hinzufügen eines neuen Feldes zu aktualisieren
  • \n
  • Funktioniert nicht für Klassen von Drittanbietern (z. B. aus Bibliotheken)
  • \n
\n\n

Wann verwenden: Nur für einfache DTOs mit 2-3 Feldern, bei denen die Leistung kritisch ist (z. B. in Spiele-Engines).

\n\n

Ansatz 2: Reflection – der universelle Soldat

\n\n

Reflection ermöglicht es, eine universelle Deep Cloning-Methode zu schreiben, die mit jedem Objekt funktioniert.

\n\n

Codebeispiel:

\n\n
public static T DeepCloneByReflection<T>(T source)\n{\n    if (source == null) return default;\n\n    var type = source.GetType();\n    \n    // Für Werttypen und Strings geben wir einfach den Wert zurück\n    if (type.IsValueType || type == typeof(string))\n        return source;\n\n    // Für Arrays\n    if (type.IsArray)\n    {\n        var arr = source as Array;\n        var clonedArr = Array.CreateInstance(type.GetElementType(), arr.Length);\n        for (int i = 0; i < arr.Length; i++)\n            clonedArr.SetValue(DeepCloneByReflection(arr.GetValue(i)), i);\n                

Blogs

Buchempfehlungen