Klassenstruktur clonen

  • VB.NET
  • .NET (FX) 4.0

Es gibt 20 Antworten in diesem Thema. Der letzte Beitrag () ist von cl10k.

    Klassenstruktur clonen

    Hallo,

    ich möchte eine unabhängige "Momentaufnahme" der Daten einer umfangreichen Klassenstruktur anlegen.

    Meine erste Anlaufstelle war dieses Tutorial aus dem Jahre 2003. Dort wird einmal der Weg über MemberwiseClone (ein Shallow-Clone reicht mir nicht aus) und einmal per Serialisieren/Deserialisieren* (funktioniert wie gewünscht) beschrieben.

    Eigentlich möchte ich nur wissen, ob das* immer noch Stand der Technik ist.

    lg
    Christian
    So, ich habe mich jetzt mal ein wenig in das Thema eingelesen und ausgetestet. Zum Erstellen eines Clones nutze ich folgende Funktion:

    VB.NET-Quellcode

    1. Function DeepClone(Of T)(ByRef orig As T) As T
    2. 'http://stackoverflow.com/questions/15584053/deep-copy-of-an-object
    3. If (Object.ReferenceEquals(orig, Nothing)) Then Return Nothing
    4. Dim formatter As New BinaryFormatter()
    5. Dim stream As New MemoryStream()
    6. formatter.Serialize(stream, orig)
    7. stream.Seek(0, SeekOrigin.Begin)
    8. Return CType(formatter.Deserialize(stream), T)
    9. End Function


    So ganz ohne Probleme läuft es aber natürlich nicht. Meine Klassen sind nicht für Serialisierung vorbereitet. Entsprechend fliegen eine ganze Menge:


    An unhandled exception of type 'System.Runtime.Serialization.SerializationException' occurred in mscorlib.dll

    Additional information: Der Typ "TestCloning.Form2+cls_rad" in Assembly "TestCloning, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" ist nicht als serialisierbar gekennzeichnet.


    Ich könnte jetzt beigehen und überall die <Serializable()> & <NonSerialized> Tags nachtragen.

    Meine Klassenstruktur ist aber relativ umfangreich. Da kommen locker 50 Teilklassen zusammen. Tatsächlich benötige ich gar nicht alles davon für meinen Clone, sondern allenfalls ein paar Dutzend Eigenschaften. Anstatt jetzt alles durchzupflügen und überall ganz feingliedrig die Tags zu setzen, würde ich mir das Leben gern leicht machen.

    Kann ich einzelne Klasseneigenschaften als Serialisierbar kennzeichnen (also in Umkehrung zu <NonSerialized>) und kann ich meiner Funktion sagen, nicht "getaggte" Elemente zu ignorieren?

    cl10k schrieb:

    würde ich mir das Leben gern leicht machen.
    Dann erstell einfach eine leere Insdtanz und übertrage die relevanten Daten.
    Allerdings ist das nicht zukunftssicher, denn iwann musst Du das erweitern, und da ist es tatsächlich besser, Du implementierst clonbare Klassen und ein anständiges Clonen.
    Wenn Du über Reflection gehst, kannst Du zumindest alle Properties iterativ clonen.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!

    cl10k schrieb:

    Meine Klassenstruktur ist aber relativ umfangreich. Da kommen locker 50 Teilklassen zusammen. Tatsächlich benötige ich gar nicht alles davon für meinen Clone, sondern allenfalls ein paar Dutzend Eigenschaften. Anstatt jetzt alles durchzupflügen und überall ganz feingliedrig die Tags zu setzen, würde ich mir das Leben gern leicht machen.
    Letztendlich kommt man beim Klonen um Feingliedrigkeit nicht herum - zumindest gibts kein Patentrezept.

    Sondern beim Klonen musste halt programmieren, wie geklont werden soll, denn "Klon" kann mal dieses mal jenes bedeuten.
    Serialisierung kann schoma viel abdecken, aber wie du siehst, nicht alles.
    Gut möglich, dass du nicht umhin kommst, ISerializable selbst zu implementieren, und dann Property für Property Angaben angeben, was am Klon mit-geklont wird, und was weggelassen werden kann.

    Übrigens ein etwas anderes Konzept verbreiter ich hier:
    ComplexConverter: alles in einen String und zurück
    ist mit Fleiß umstritten, aber für dich nützlich kann grade @hal2000s Alternative sein, die demonstriert nämlich eine ISerializable-Implementation.

    Jdfs mein Konzept ist grade kein Klon, sondern es muss ein "unbeschriebenes Blatt" zu instanzieren möglich sein, und dessen Properties werden alle - ganz feingliedrig - gesetzt.
    Wie gesagt: Das ist kein Klonen, bei dem eine neues Objekt entsteht, sondern es ist ein Befüllen.
    Und während beim Serialisieren quasi das ganze Objekt in eine Datei umgewandelt wird, wird beim ComplexConverter nur die Befüllung umgewandelt.
    Also wenn dein Kram wirklich kompliziert wird, dann dürfte der Thread für dich interessant sein, ob du dich nu für ISerialzable entscheidest oder für ComplexConverter.
    Da bin ich wieder^^

    Ich habe jetzt mal ein wenig mit ISerializable herumgespielt, aber die "Magie" erschließt sich mir noch nicht...

    Um den Kram auszutesten, habe ich eine primitive Klassenstruktur Auto mit mehreren Unterklassen erstellt, die meinem Anwendungsfall recht ähnlich ist. Alle Klassen haben Eigenschaften die mich für den Clone interessieren und Eigenschaften die für den Clone ohne Belang sind (alle Bla... properties).

    Um klar zu definieren, was serialisiert werden soll, existieren in cls_Auto ein angepasster Konstruktor und eine entsprechende GetObjectDataInfo-Methode. Das funktioniert soweit auch. Ich kann in cls_Auto angeben, welche Eigenschaften, auch von Unterklassen, serialisiert werden sollen.

    Trotzdem kommt es mir so vor, als das ich bisher nur an der Oberfläche kratze und mich ziemlich ungelenk anstelle. Was kann man besser machen?

    Vor allem interessiert mich folgendes: Ich würde gern direkt in den Unterklassen angeben was serialisiert werden soll. So habe ich z.B. in cls_Getriebe einen entsprechenden Konstruktor und GetObjectDataInfo implementiert, diese werden aber vollständig ignoriert. Serialisiert wird nur, was ich in cls_Auto.GetObjectDataInfo und cls_Auto.New angegeben habe. Wie macht man es richtig?

    Und nebenbei, was machen die Surogatoren die Hal2000 hier einsetzt?
    ----
    Kurz zum angehängten Code:
    Ein einfaches Form mit 3 Buttons. Buttons1 und 3 zeigen einfach die Klassenproperties von Auto1 und Auto2 im Output Window an. Button2 ruft die Clone-Funktion auf. (Alles quick&dirty)


    Spoiler anzeigen

    VB.NET-Quellcode

    1. Imports System.Runtime.Serialization
    2. Imports System.Runtime.Serialization.Formatters.Binary
    3. Imports System.IO
    4. Public Class Form3
    5. Public Auto1 As New cls_Auto
    6. Public Auto2 As New cls_Auto
    7. #Region "Buttons"
    8. Private Sub btn_ShowAuto1_Click(sender As System.Object, e As System.EventArgs) Handles btn_ShowAuto1.Click
    9. ShowAuto(Auto1)
    10. End Sub
    11. Private Sub btn_CloneAuto1_Click(sender As System.Object, e As System.EventArgs) Handles btn_CloneAuto1.Click
    12. Auto2 = DeepClone(Auto1)
    13. Auto2.Marke = "Clone von DACIA"
    14. End Sub
    15. Private Sub btn_ShowAuto2_Click(sender As System.Object, e As System.EventArgs) Handles btn_ShowAuto2.Click
    16. ShowAuto(Auto2)
    17. End Sub
    18. #End Region
    19. #Region "Auto"
    20. <Serializable()> _
    21. Public Class cls_Auto
    22. Implements ISerializable
    23. Public Rad As New cls_rad
    24. Public Motor As New cls_Motor
    25. Public Getriebe As New cls_Getriebe
    26. Public Property blaAuto() As Integer
    27. Public Property Masse() As Integer
    28. Public Property Marke() As String
    29. Public Sub New()
    30. End Sub
    31. Public Sub New(ByVal Info As SerializationInfo, ByVal Context As StreamingContext)
    32. With Info
    33. Masse = .GetInt32("Masse")
    34. Motor.PS = .GetInt32("MotorPS")
    35. End With
    36. End Sub
    37. Sub getobjectdatainfo(ByVal Info As SerializationInfo, ByVal Context As StreamingContext) Implements ISerializable.GetObjectData
    38. With Info
    39. .AddValue("Masse", Masse)
    40. .AddValue("MotorPS", Motor.PS)
    41. End With
    42. End Sub
    43. End Class
    44. Public Class cls_Motor
    45. Public Property blaMotor() As Integer
    46. Public Property PS() As Integer
    47. End Class
    48. Public Class cls_Getriebe
    49. Implements ISerializable
    50. Public Property blaGetriebe() As Integer
    51. Public Property Masse() As Integer
    52. Public Sub New()
    53. End Sub
    54. Public Sub New(ByVal Info As SerializationInfo, ByVal Context As StreamingContext)
    55. With Info
    56. Masse = .GetInt32("Masse")
    57. End With
    58. End Sub
    59. Sub getobjectdatainfo(ByVal Info As SerializationInfo, ByVal Context As StreamingContext) Implements ISerializable.GetObjectData
    60. With Info
    61. .AddValue("Masse", Masse)
    62. End With
    63. End Sub
    64. End Class
    65. Public Class cls_rad
    66. Public felge As New cls_Felge
    67. Public reifen As New cls_Reifen
    68. Public Property blaRad() As Integer
    69. Public Property Masse() As Integer
    70. End Class
    71. Public Class cls_Felge
    72. Public Property blaFelge() As Integer
    73. Public Property Masse() As Integer
    74. End Class
    75. Public Class cls_Reifen
    76. Public Property blaReifen() As Integer
    77. Public Property Masse() As Integer
    78. End Class
    79. #End Region
    80. Private Sub Form3_Load(sender As Object, e As System.EventArgs) Handles Me.Load
    81. Auto1.Marke = "Dacia"
    82. Auto1.Masse = 1500
    83. Auto1.blaAuto = 111
    84. Auto1.Rad.Masse = 100
    85. Auto1.Rad.blaRad = 222
    86. Auto1.Rad.felge.Masse = 75
    87. Auto1.Rad.felge.blaFelge = 333
    88. Auto1.Rad.reifen.Masse = 25
    89. Auto1.Rad.reifen.blaReifen = 444
    90. Auto1.Motor.blaMotor = 555
    91. Auto1.Motor.PS = 25
    92. Auto1.Getriebe.blaGetriebe = 666
    93. Auto1.Getriebe.Masse = 200
    94. End Sub
    95. Private Sub ShowAuto(ByVal tmpAuto As cls_Auto)
    96. Debug.WriteLine("======================================")
    97. Debug.WriteLine("AutoMarke: " & tmpAuto.Marke)
    98. Debug.WriteLine("AutoMasse: " & tmpAuto.Masse)
    99. Debug.WriteLine("AutoBla: " & tmpAuto.blaAuto)
    100. Debug.WriteLine("-------------------")
    101. Debug.WriteLine("RadMasse: " & tmpAuto.Rad.Masse)
    102. Debug.WriteLine("RadBla: " & tmpAuto.Rad.blaRad)
    103. Debug.WriteLine("-------------------")
    104. Debug.WriteLine("FelgeMasse: " & tmpAuto.Rad.felge.Masse)
    105. Debug.WriteLine("FelgeBla: " & tmpAuto.Rad.felge.blaFelge)
    106. Debug.WriteLine("-------------------")
    107. Debug.WriteLine("ReifenMasse: " & tmpAuto.Rad.reifen.Masse)
    108. Debug.WriteLine("ReifenBla: " & tmpAuto.Rad.reifen.blaReifen)
    109. Debug.WriteLine("-------------------")
    110. Debug.WriteLine("MotorBla: " & tmpAuto.Motor.blaMotor)
    111. Debug.WriteLine("MotorPS: " & tmpAuto.Motor.PS)
    112. Debug.WriteLine("-------------------")
    113. Debug.WriteLine("GetriebeBla: " & tmpAuto.Getriebe.blaGetriebe)
    114. Debug.WriteLine("GetriebeMasse: " & tmpAuto.Getriebe.Masse)
    115. End Sub
    116. Function DeepClone(Of T)(ByRef orig As T) As T
    117. 'http://stackoverflow.com/questions/15584053/deep-copy-of-an-object
    118. ' Don't serialize a null object, simply return the default for that object
    119. If (Object.ReferenceEquals(orig, Nothing)) Then Return Nothing
    120. Dim formatter As New BinaryFormatter()
    121. Dim stream As New MemoryStream()
    122. formatter.Serialize(stream, orig)
    123. stream.Seek(0, SeekOrigin.Begin)
    124. Return CType(formatter.Deserialize(stream), T)
    125. End Function
    126. End Class


    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „cl10k“ ()

    ich habs jetzt mal getestet - also wenn du im Iserializable nur 2 Properties serialisierst, dann sind im Clon halt nur 2 Properties serialisiert.

    VB.NET-Quellcode

    1. 'cla_Auto
    2. Sub getobjectdatainfo(ByVal Info As SerializationInfo, ByVal Context As StreamingContext) Implements ISerializable.GetObjectData
    3. With Info
    4. .AddValue("Masse", Masse)
    5. .AddValue("MotorPS", Motor.PS)
    6. End With
    7. End Sub
    so gehts:

    VB.NET-Quellcode

    1. #Region "Auto"
    2. <Serializable> _
    3. Public Class cls_Auto
    4. Public Rad As New cls_rad
    5. Public Motor As New cls_Motor
    6. Public Getriebe As New cls_Getriebe
    7. Public Property blaAuto() As Integer
    8. Public Property Masse() As Integer
    9. Public Property Marke() As String
    10. End Class
    11. <Serializable> _
    12. Public Class cls_Motor
    13. Public Property blaMotor() As Integer
    14. Public Property PS() As Integer
    15. End Class
    16. <Serializable> _
    17. Public Class cls_Getriebe
    18. Public Property blaGetriebe() As Integer
    19. Public Property Masse() As Integer
    20. End Class
    21. <Serializable> _
    22. Public Class cls_rad
    23. Public felge As New cls_Felge
    24. Public reifen As New cls_Reifen
    25. Public Property blaRad() As Integer
    26. Public Property Masse() As Integer
    27. End Class
    28. <Serializable> _
    29. Public Class cls_Felge
    30. Public Property blaFelge() As Integer
    31. Public Property Masse() As Integer
    32. End Class
    33. <Serializable> _
    34. Public Class cls_Reifen
    35. Public Property blaReifen() As Integer
    36. Public Property Masse() As Integer
    37. End Class
    38. #End Region
    also er erkennt das durchaus. Nur wenn du ISerializable implementierst, dann hält er sich natürlich daran.
    Also implementiere es nicht.
    Drängler!

    Mit deiner Methode fange ich mir aber wieder alle vorhandenen Properties ein. Ich möchte ja nur bestimmte "ausgewählte" Eigenschaften clonen.

    Ich habe jetzt cls_Auto wie folgt geändert:

    VB.NET-Quellcode

    1. Public Sub New(ByVal Info As SerializationInfo, ByVal Context As StreamingContext)
    2. With Info
    3. Masse = .GetInt32("Masse")
    4. Motor.PS = .GetInt32("MotorPS")
    5. Getriebe = DirectCast(.GetValue("Getriebe", GetType(cls_Getriebe)), cls_Getriebe) '<----
    6. End With
    7. End Sub
    8. Sub getobjectdatainfo(ByVal Info As SerializationInfo, ByVal Context As StreamingContext) Implements ISerializable.GetObjectData
    9. With Info
    10. .AddValue("Masse", Masse)
    11. .AddValue("MotorPS", Motor.PS)
    12. .AddValue("Getriebe", Getriebe) '<----
    13. End With
    14. End Sub


    und das funktioniert. Ich clone jetzt genau die Infos aus Getriebe, die ich dort lokal in der getobjectdatainfo angeben habe :)


    Trotzdem beschleicht mich das Gefühl, ISerializable eher wenig elegant zu benutzen. Das gecaste geht mir auch aufn Keks...

    cl10k schrieb:

    Ich möchte ja nur bestimmte "ausgewählte" Eigenschaften clonen.
    ah - hab ich nciht gewusst.
    Du kannst die BackingFields von Properties, die nicht serialisiert werden sollen, mit <NonSerialized> attributieren.

    VB.NET-Quellcode

    1. <Serializable> _
    2. Public Class cls_Felge
    3. Public Property blaFelge() As Integer
    4. <NonSerialized> _
    5. Private _Masse As Integer
    6. Public Property Masse() As Integer
    7. Get
    8. Return _Masse
    9. End Get
    10. Set(value As Integer)
    11. _Masse = value
    12. End Set
    13. End Property
    14. End Class

    Ja, <nonserialized> wäre eine Möglichkeit. Aber in meiner tatsächlichen Klassenstruktur komme ich auf mehrere Hundert Properties. Für meinen Clone brauche ich aber nur so 30 bis maximal 50 davon. Ich bin tatsächlich zu faul, jede einzelne Property entsprechend zu taggen.

    Mit der aktuellen Lösung, brauche ich nur zu definieren, was ich tatsächlich gecloned haben möchte. Also genau das was ich mir ursprünglich (im Eingangspost) erhofft hatte.

    Schön wenn etwas mal funktioniert ohne elendig herumprobieren zu müssen :)

    (Der Clone dient dafür, einen Snapshot von einer Klassenstruktur anzufertigen und diesen Snapshot dann per AsyncWorker an ein externes CAD-Programm zu schicken. In der Zwischenzeit steht es dem User frei die GUI weiter zu benutzen und damit die Daten in der originalen Klassenstruktur zu ändern)

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „cl10k“ ()

    Du sprichst immer von klonen - serialisieren ist aber bisserl mehr (und glaub auch umständlicher).
    also wenn du nur klonen willst - das ginge einfacher als das Gemurkel mit ISerializable zu implementieren. (und geht übrigens ohne Cast).

    Einfach eine Methode Clone schreiben, die ein neues cls_Auto zurückgibt, mit den Eigenschaften, die du übertragen haben willst.

    Womit wir wieder bei der Feingliedrigkeit von vor 10 posts rauskämen :P
    Ja, es ist für mich halt ein langsames Rantasten an ein unbekanntes Thema...

    Ursprünglich wollte ich eigentlich nur eine Momentaufnahme der Daten in meiner Klassenstruktur haben (zu diesem Zeitpunkt hatte ich keine Ahnung was Serialization überhaupt ist...)

    Verrätst du mir noch, wie ich an dem Cast vorbeikomme?

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „cl10k“ ()