Als nicht-serialisierbar gekennzeichnete Objekte speichern

Beschreibung
Wenn komplette Objekte gespeichert werden müssen, dann ist Serialisation wohl eine einfache und schnelle Möglichkeit dies zu bewerkstelligen. Das gilt allerdings nur für Klassen, die über SerializableAttribute verfügen. Ansonsten kann man wichtige Daten in ein serialisierbares Objekt speichern. Wir möchten Ihnen eine andere Möglichkeit zeigen, wie man Klassen speichern kann, die nicht über dieses Attribut verfügen.
Hinweise zu dieser Klasse
Diese Vorgehensweise entspricht nicht der üblichen Implementierung der Serialisierung und widerspricht somit der durchdachten Struktur des Frameworks. Diese Klasse arbeitet rekursiv und ist nicht in der Lage Ringbeziehungen zu erkennen. Dadurch kann es zu einer StackOverflowException kommen. Weiterhin bietet sie keine vollständige Funktionalität, beispielsweise eine komplette Ausnahmebehandlung. Wir möchten hierbei lediglich einige Möglichkeiten des Frameworks bezüglich Reflection und Rekursion aufzeigen um Denkanstöße zu geben. Für einen effizienten Einsatz ist sie allerdings kaum zu verwenden, da Reflection relativ langsam arbeitet.

Wir wollen nun aber sukzessiv vorgehen und machen uns erst einmal Gedanken über die Struktur der Klasse. Als Grundbaustein nehmen wir eine generische Klasse, wodurch wir den Typ des zu verwaltenden Objektes angeben können. Im Konstruktor soll das Objekt übergeben werden, welches auf diese Weise gespeichert werden soll. Unser Klasse soll ähnliche Methoden aufweisen wie die im Framework implementierte Serialization, wodurch wir 2 Methoden implementieren: Serialize und Deserialize.
Nun müssen wir uns überlegen, wie wir die Eigenschaften des zu verwaltenden Objektes speichern. Dafür ist es sinnvoll die Eigenschaften und die zugehörigen Werte in einer Klasse abzubilden, die serialisiert werden kann. Wir verwenden dafür das generische Dictionary. Der Key ist der Name der Eigenschaft und Value ist dabei der Wert der Eigenschaft des zu speichernden Objektes.
Dabei stoßen wir aber auf ein Problem. Was ist, wenn der Wert der Eigenschaft vom Typ einer Klasse ist, die ebenfalls als nicht-serialisierbar gekennzeichnet ist? Für den Fall suchen wir uns eine rekursive Lösung. Wird ein Objekt gefunden, welches nicht serialisierbar ist, dann erzeugen wir eine neue Instanz der generischen Dictionary-Klasse und bilden diese nicht-serialisierbare Klasse damit ab. Dieses Dictionary wird nun als Value für die Eigenschaft gespeichert anstelle des Wertes selbst. Dadurch erhalten wir eine Baumstruktur des zu verwaltenden Objektes mit allen Eigenschaften und zugehörigen Werten.
Umgesetzt könnte die Klasse in etwa so aussehen:
VBC#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
''' <summary>
''' Serialisiert Instanzen von Klassen, die als nicht-serialisierbar gekennzeichnet sind.
''' </summary>
''' <typeparam name="T">Der zu verwaltende Datentyp.</typeparam>
''' <remarks></remarks>
Public Class SerializationObject(Of T)
 
#Region " Deklarationen - Felder "
 
   Private pToSerialize As T
 
#End Region
 
#Region " Konstruktoren "
 
   ''' <summary>
   ''' Initialisiert eine neue Instanz der generischen SerializationObject-Klasse.
   ''' </summary>
   ''' <param name="toSerialize"></param>
   ''' <remarks></remarks>
   Public Sub New(ByVal toSerialize As T)
      pToSerialize = toSerialize
   End Sub
 
#End Region
 
#Region " Eigenschaften "
 
   ''' <summary>
   ''' Ruft das zu verwaltende Objekt ab.
   ''' </summary>
   ''' <value></value>
   ''' <returns>Das zu verwaltende Objekt.</returns>
   ''' <remarks></remarks>
   Public ReadOnly Property Value() As T
      Get
         Return pToSerialize
      End Get
   End Property
 
#End Region
 
#Region " Methoden - Function "
 
   ''' <summary>
   ''' Gibt an, ob das Objekt als serialisierbar gekennzeichnet ist.
   ''' </summary>
   ''' <param name="obj">Das Objekt, welches geprüft werden soll.</param>
   ''' <returns>true, wenn das Objekt als serialisierbar gekennzeichnet ist, ansonsten false.
   ''' </returns>
   ''' <remarks></remarks>
   Private Function HasSerializableAttribute(ByVal obj As Object) As Boolean
      ' Iteriere durch alle Attribute des Objektes
      For Each attr As Object In obj.GetType().GetCustomAttributes(False)
         ' Prüfe, ob SerializableAttribute; true -> true zurückgeben
         If attr.GetType Is GetType(SerializableAttribute) Then Return True
      Next
 
      ' false zurückgeben
      Return False
   End Function
 
   ''' <summary>
   ''' Bildet das angegebene Objekt mit den Eigenschaften und den zugehörigen Werten als 
   ''' serialisierbares Dictionary ab und gibt dieses zurück.
   ''' </summary>
   ''' <param name="obj">Das Objekt, welches als serialisierbares Dictionary abgebildet werden 
   ''' soll.</param>
   ''' <returns>Das Dictionary, welches die Eigenschaften und zugehörigen Werte des angegebenen 
   ''' Objektes abbildet.</returns>
   ''' <remarks></remarks>
   Private Function ReadProperties(ByVal obj As Object) As Dictionary(Of String, Object)
      Dim dicToSave As New Dictionary(Of String, Object)
      Dim pi As System.Reflection.PropertyInfo
 
      ' Iteriere durch jede Property des Objektes
      For Each pi In obj.GetType().GetProperties(Reflection.BindingFlags.Instance Or _
                                                 Reflection.BindingFlags.Public)
         ' Prüfe, ob Eigenschaft über einen Get- und Set-Accessor verfügt
         If pi.CanRead Then
            ' true -> Wert lesen
            Dim val As Object = pi.GetValue(obj, New Object() {})
 
            ' Prüfe, ob der Wert serialisierbar ist
            If HasSerializableAttribute(val) Then
               ' true -> Name und Wert hinzufügen
               dicToSave.Add(pi.Name, val)
            Else
               ' false -> Name und Wert als Dictionary hinzufügen
               dicToSave.Add(pi.Name, ReadProperties(val))
            End If
         End If
      Next
 
      ' Dictionary zurückgeben
      Return dicToSave
   End Function
 
#End Region
 
#Region " Methoden - Sub "
 
   ''' <summary>
   ''' Deserialisiert die gespeicherten Daten aus einer Datei in das verwaltete Objekt.
   ''' </summary>
   ''' <param name="path">Der Pfad der Datei mit den Daten.</param>
   ''' <remarks></remarks>
   Public Sub Deserialize(ByVal path As String)
      Try
         ' Das zu ladende Dictionary
         Dim dictToLoad As Dictionary(Of String, Object)
 
         ' Verwende den Datenstream
         Using fs As New System.IO.FileStream(path, IO.FileMode.OpenOrCreate)
            ' Formatter erzeugen
            Dim bf As New Runtime.Serialization.Formatters.Binary.BinaryFormatter
 
            ' Daten deserialisieren
            dictToLoad = DirectCast(bf.Deserialize(fs), Dictionary(Of String, Object))
 
            ' Werte der Eigenschaften setzen
            Call SetProperties(pToSerialize, dictToLoad)
         End Using
 
      Catch ex As Exception
         Debug.WriteLine(ex.Message)
      End Try
   End Sub
 
   ''' <summary>
   ''' Serialisiert das verwaltete Objekt in die angegebene Datei.
   ''' </summary>
   ''' <param name="path">Der Pfad der Datei, in welche die Daten serialisiert werden sollen.</param>
   ''' <remarks></remarks>
   Public Sub Serialize(ByVal path As String)
      Try
         ' Das zu speichernde Dictionary
         Dim dicToSave As Dictionary(Of String, Object) = ReadProperties(pToSerialize)
 
         ' Verwende den Datenstream
         Using fs As New System.IO.FileStream(path, IO.FileMode.OpenOrCreate)
            ' Formatter erzeugen
            Dim bf As New Runtime.Serialization.Formatters.Binary.BinaryFormatter
 
            ' Daten serialisieren
            bf.Serialize(fs, dicToSave)
         End Using
 
      Catch ex As Exception
         Debug.WriteLine(ex.Message)
      End Try
   End Sub
 
   ''' <summary>
   ''' Setzt die im properties-Parameter angegebenen Eigenschaften mit zugehörigen Werten für das im 
   ''' obj-Parameter angegebene Objekt.
   ''' </summary>
   ''' <param name="obj">Das Objekt, dessen Eigenschaften gesetzt werden sollen.</param>
   ''' <param name="properties">Das Dictionary mit den Eugenschaften und zugehörigen Werten.</param>
   ''' <remarks></remarks>
   Private Sub SetProperties(ByVal obj As Object, ByVal properties As Dictionary(Of String, Object))
      ' Variablen deklarieren
      Dim pi As System.Reflection.PropertyInfo = Nothing
      Dim propValue As Object = Nothing
 
      ' Iteriere durch jeden Eintrag im Dictionary
      For Each kvp As KeyValuePair(Of String, Object) In properties
         ' Entsprechende Eigenschaft des Objektes finden
         pi = obj.GetType().GetProperty(kvp.Key, Reflection.BindingFlags.Instance Or _
                                        Reflection.BindingFlags.Public)
 
         ' Prüfe, ob die Eigenschaft gefunden wurde
         If pi IsNot Nothing Then
            ' true -> Prüfe, ob es sich um ein Dictionary handelt und der Datentyp der Eigenschaft
            ' kein Dictionary ist (das Dictionary bildet also eine Klasse ab)
            If TypeOf kvp.Value Is Dictionary(Of String, Object) AndAlso _
               pi.PropertyType IsNot GetType(Dictionary(Of String, Object)) Then
               ' true -> Instanz erstellen und als zu schreibenden Wert speichern
               propValue = Activator.CreateInstance(pi.PropertyType)
 
               ' Methode rekursiv aufrufen um die Eigenschaften der erzeugten Instanz zu setzen
               Call SetProperties(propValue, DirectCast(kvp.Value, Dictionary(Of String, Object)))
            Else
               ' false -> Wert lesen und als zu schreibenden Wert speichern
               propValue = kvp.Value
            End If
 
            ' Prüfe, ob die Eigenschaft geschrieben werden kann
            If pi.CanWrite Then
               ' true -> Wert speichern
               pi.SetValue(obj, propValue, New Object() {})
            End If
         End If
      Next
   End Sub
 
#End Region
 
End Class
1
Kein Code vorhanden