Zwei Wege des Klonens von Objekten

Veröffentlicht: 12. Jun 2003 | Aktualisiert: 24. Jun 2004

Von Mathias Schiffer

Um statt eines weiteren Verweises auf ein Referenzobjekt eine Kopie des Objekts zu erhalten, müssen Sie das Objekt klonen. Dieser Artikel zeigt zwei unterschiedlich wirkende Möglichkeiten auf, Ihre eigenen Objekte dafür klonbar zu gestalten.

Auf dieser Seite

 Klonen per MemberwiseClone
 Klonen per Serialisierung und Deserialisierung

Eine Objektkopie soll eine weitere Ausfertigung eines Objekts sein, das die gleichen Eigenschaftenwerte hat, aber eben nicht nur ein weiterer Verweis auf das originale Objekt. Denken Sie an Zahlen oder andere Wertetypen, ist die Sache einfach: In der gleichen Eigenschaft der Kopie soll der gleiche Zahlenwert wie in der Eigenschaft des Originals vorzufinden sein. Im Regelfall soll auch der Wert dieser Eigenschaft der Kopie änderbar sein, ohne den Wert im originalen Objekt irgendwie zu beeinflussen.

Klonen per MemberwiseClone

Alles kein Problem dank der von der Basis allen Typendaseins System.Object zur Verfügung gestellten Funktion MemberwiseClone, werden Sie sagen - und Sie haben Recht. Die Implementation einer so arbeitenden Clone-Funktion gestaltet sich nahezu primitiv:

Public Class MyObject 
  Implements ICloneable 
  Public <SPAN class=460245922-09072003>Referenz </SPAN>As Object 
  Public Num<SPAN class=460245922-09072003>m</SPAN>er As Integer  
  Public Function Clone() As Object _ 
   Implements ICloneable.Clone 
 ' Gibt eine "flache" Kopie dieses Objekts zurück 
 Return Me.MemberwiseClone() 
  End Function 
End Class

Einen Haken aber hat die Sache: Referenztypen (die auf Objekte verweisen) in Eigenschaften werden nicht kopiert, sondern es wird lediglich der Verweis auf das von ihnen gehaltene Objekt kopiert. Sprechen Sie nun das Objekt in dieser Eigenschaft des Klons an, so arbeiten Sie nicht mit einer Kopie des gehaltenen Objekts, sondern mit dem Original. Probieren Sie es aus:

Dim Original As New MyObject() 
Dim Klon As New MyObject() 
Dim Objekt As New MyObject() 
  Objekt.Nummer = 1 
  Original.Referenz = Objekt 
  Klon = Original.Clone 
  If Klon.Referenz Is Objekt Then 
 MsgBox("Das Referenzobjekt des Klons ist das Objekt.") ' OK 
  Else 
 MsgBox("Das Referenzobjekt selber wurde kopiert.") 
  End If 
  ' Auswirkung: 
  Objekt.Nummer = 666 
  MsgBox(Klon.Referenz.Nummer.ToString) ' => 666 statt 1

Natürlich ist dieses Verhalten in vielen Fällen exakt so gewollt - dann gibt es selbstverständlich auch kein Problem; schönen Heimweg, fahren Sie bitte vorsichtig.

Wo Sie aber nicht eine Kopie des Objektverweises erreichen wollen, sondern eine Kopie des in der Eigenschaft gehaltenen Objekts, das Sie ohne Auswirkungen auf das geklonte Original bearbeiten können wollen, ist MemberwiseClone - so einfach es die vorherige Aufgabe löste - keine Lösung für Sie.

 

Klonen per Serialisierung und Deserialisierung

Der folgende Lösungsansatz hilft diesem Problem ab: Das Konzept hinter dem Vorgehen ist, mithilfe eines BinaryFormatters das zu klonende Originalobjekt inklusive aller in ihm enthaltenen Informationen in einen Stream im Speicher zu serialisieren und daraufhin diese gesammelten Informationen wieder in ein neues Objekt zu deserialisieren. Dabei wird der Vorteil des Formatters genutzt, komplette Objektstrukturen durchwandern zu können. Da allein ein Verweis auf ein Objekt zum Zeitpunkt der Serialisierung bei der späteren Deserialisierung im Zweifelsfall völlig nutzlos wäre, ist dieses Vorgehen des Formatters auch notwendig.

Einen kleinen Haken hat aber auch dieses Konzept: Damit der Formatter sämtliche vom Originalobjekt aus erreichbaren Objekte serialisieren kann, müssen sämtliche dieser Objekte natürlich dem Anspruch genügen, überhaupt serialisierbar zu sein. Für die Standardtypen des .NET Frameworks sollten Sie sich dahingehend keine Sorgen machen. Und für Ihre eigenen Objekte ist die Erfüllung dieses Anspruchs ein Kinderspiel: Sie versehen Ihre beteiligten Klassen einfach mit dem Attribut "Serializable" und schon kümmert sich das .NET Framework um den Rest. Lediglich für den Fall, dass von Ihnen im zu klonenden Original genutzte Typen Dritter nicht serialisierbar sein sollten, wird dieses Ausschlusskriterium relevant.

Hier also nun der Sourcecode für ein anderes klonbares Objekt mit einer dergestalt implementierten Klonfunktion CloneDeep:

<Serializable()> Public Class MyOtherObject 
  Implements ICloneable 
  Public Referenz As Object 
  Public Nummer As Integer 
  Public Function CloneDeep() As Object _ 
   Implements ICloneable.Clone 
 ' Gibt eine vollständige Kopie dieses Objekts zurück. 
 ' Voraussetzung ist die Serialisierbarkeit aller beteiligten 
 ' Objekte. 
 Dim Stream As New System.IO.MemoryStream(50000) 
 Dim Formatter As New _ System.Runtime.Serialization.Formatters.Binary.BinaryFormatter() 
 ' Serialisierung über alle Objekte hinweg in einen Stream 
 Formatter.Serialize(Stream, Me) 
 ' Zurück zum Anfang des Streams und... 
 Stream.Seek(0, System.IO.SeekOrigin.Begin) 
 ' ...aus dem Stream in ein Objekt deserialisieren 
 CloneDeep = Formatter.Deserialize(Stream) 
 Stream.Close() 
  End Function 
End Class

Auch hier soll wieder ein Stück Testcode aufzeigen, dass diesmal die Objektstruktur tatsächlich komplett kopiert wird:

Dim Original As New MyOtherObject() 
Dim Klon As New MyOtherObject() 
Dim Objekt As New MyOtherObject() 
  Objekt.Nummer = 1 
  Original.Referenz = Objekt 
  Klon = Original.CloneDeep 
  If Klon.Referenz Is Objekt Then 
 MsgBox("Das Referenzobjekt des Klons ist das Objekt.") 
  Else 
 MsgBox("Das Referenzobjekt selber wurde kopiert.") ' OK 
  End If 
  ' Auswirkung: 
  Objekt.Nummer = 666 
  MsgBox(Klon.Referenz.Nummer.ToString) ' => 1 (OK)