7.7 Serialisierung
Die in Abschnitt 7.6.4, »Komplexe binäre Daten«, gezeigte Speicherung und Wiederherstellung ist eine Möglichkeit, Daten zur Wiederverwendung zu sichern. Viel einfacher ist es, sich auf die Möglichkeiten von .NET zu stützen. Der Prozess, Daten in einen Strom (zum Beispiel eine Datei) zu speichern, wird Serialisierung genannt.
Die Serialisierung kann ein im Hauptspeicher befindliches Objekt in ein bestimmtes Format konvertieren und in einen Strom schreiben. Außerdem kann das Objekt in seinem ursprünglichen Format rekonstruiert werden. Diese Prozesse laufen automatisch ab. Die gespeicherten Daten bestehen aus dem Namen der Anwendung, dem Namen der Klasse und den Objektdaten. Dadurch wird die spätere Rekonstruktion in einer exakten Kopie möglich.
Ein zu sichernder Objektzustand ist vollständig durch die Felder des Objekts beschrieben. Alle weiteren Informationen sind mit der Klasse verbunden und nicht Teil des Zustandes.
7.7.1 Serialisierungsverfahren
Die .NET-Klassenbibliothek hat für die Formatierung der Objektdaten drei Klassen (siehe Tabelle 7.21). Sie bestimmen das Datenformat, das in den Strom geschrieben wird bzw. aus ihm gelesen wird.
Klasse | Übertragungsformat |
BinaryFormatter |
Binäres Format, das zirkuläre Referenzen unterstützt |
SoapFormatter |
SOAP-Format (Simple Object Access Protocol), das zirkuläre Referenzen unterstützt. System.Runtime.Serialization.Formatters.Soap.dll muss eingebunden werden. |
XmlSerializer |
XML-Format, das zirkuläre Referenzen nicht unterstützt. System.Xml.dll muss eingebunden werden. |
Alle drei implementieren die Schnittstelle IFormatter. Kommen Sie mit keiner der drei Klassen zurecht, müssen Ihre eigenen Klassen diese Schnittstelle implementieren.
Public Interface IFormatter
Function Deserialize(serializationStream As Stream) As Object
Sub Serialize(serializationStream As Stream, graph As Object)
Property Binder As SerializationBinder
Property Context As StreamingContext
Property SurrogateSelector As IsurrogateSelector
End Interface |
Die Speicherung von Objektdaten in einen Strom übernimmt Serialize. Das erste Argument spezifiziert den Strom, in den geschrieben wird; oft ist das ein FileStream. Das zu serialisierende Objekt wird im zweiten Parameter übergeben.
Die Methode Deserialize rekonstruiert ein Objekt. Das Argument spezifiziert den Strom, aus dem gelesen wird; oft ist das ein FileStream. Der Rückgabewert ist vom Typ Object und muss deshalb noch in den richtigen Typ konvertiert werden.
7.7.2 Testklassen
Alle Objekte einer Klasse, die das Attribut Serializable hat, sind serialisierbar.
<Serializable()> Public Class ClassA ...
Wenn Sie versuchen, Objekte von Klassen zu serialisieren, die das Attribut nicht haben, wird die Ausnahme SerializationException ausgelöst. Alle Felder der Klasse ClassA, unabhängig davon, ob sie privat oder öffentlich deklariert sind, werden von der Serialisierung erfasst. Es gibt aber auch eine Einschränkung:
Lokale und klassengebundene Variablen nehmen an einem Serialisierungsprozess nicht teil. |
Die meisten Beispiele zur Serialisierung verwenden eine Klasse ClassA mit einem privaten und einem öffentliches Feld, die beide über einen Konstruktor initialisiert werden.
'...\IO\Serialisierung\ClassA.vb |
Namespace EA <Serializable()> Public Class ClassA Public intVar As Integer Private strVar As String Public Sub New() End Sub Public Sub New(ByVal x As Integer, ByVal str As String) intVar = x strVar = str End Sub Property Name() As String Get Return strVar End Get Set(ByVal value As String) strVar = value End Set End Property End Class End Namespace
Einige Beispiele verwenden außerdem eine Klasse Person mit einem öffentlichen Feld:
'...\IO\Serialisierung\Binär.vb |
Namespace EA <Serializable()> Public Class Person Public Name As String Public Sub New(str As String) Name = str End Sub End Class End Namespace
Bei der Serialisierung greift der Prozess den Inhalt von intVar und strName und speichert ihn entweder in einer Datei, im Netzwerk oder in einer Datenbank. Die Deserialisierung belegt diese beiden mit den gelesenen Werten.
7.7.3 Serialisierung mit BinaryFormatter
Im folgenden Beispiel verwenden wir BinaryFormatter zur Formatierung der über einen Dateistrom geschickten Daten (zu ClassA siehe Abschnitt 7.7.2, »Testklassen«). Nach dessen Instanziierung erzeugen wir ein Testobjekt. Die Methode Serialize aus der Klasse BinaryFormatter speichert dieses Objekt in einem FileStream, der es wiederum in der angegebenen Datei speichert. Durch den Wert FileMode.Create ist sichergestellt, dass die Datei keine zusätzlichen Daten enthält. Danach lesen wir ein Objekt mit Deserialize aus einem FileStream, der auf dieselbe Datei zugreift, und konvertieren es in den richtigen Typ. Schließlich geben wir das gelesene Objekt aus. Um das Beispiel kurz zu halten, ist alles in einer Methode.
'...\IO\Serialisierung\Binär.vb |
Option Strict On Imports System.IO Namespace EA Module Binär Sub Test() Dim path As String = IO.Path.GetTempFileName() Dim fs As FileStream ' Objekt und Formatierer erzeugen Dim bf As New System.Runtime.Serialization.Formatters.Binary. _ BinaryFormatter() Dim obj As New ClassA(310, "Peter") ' speichern fs = New FileStream(path, FileMode.Create) bf.Serialize(fs, obj) fs.Close() ' lesen fs = New FileStream(path, FileMode.Open) Dim obj2 As ClassA = CType(bf.Deserialize(fs), ClassA) fs.Close() ' Ausgabe Console.WriteLine("{{intVar={0}, Name={1}}}", obj2.intVar, obj2.Name) File.Delete(path) Console.ReadLine() End Sub End Module End Namespace
Die Ausgabe zeigt die Daten des mit New erzeugten Objekts; die Serialisierung hat geklappt.
{intVar=310, Name=Peter}
Serialisierung mehrerer Objekte
Der Serialisierungsprozess kann auch im Typ unterschiedliche Objekte erfassen. Für jedes Objekt erfolgt ein eigener Aufruf von Serialize bzw. Deserialize für denselben Strom. Die Reihenfolge der Objekte ist beim Schreiben und Lesen identisch. Das ist ein FIFO-Prinzip (first in – first out): Das zuerst serialisierte Objekt muss auch als Erstes wieder deserialisiert werden. Bitte beachten Sie, dass das Lesen über das Ende des Datenstroms hinaus eine Ausnahme zur Folge hat.
Anstatt jedes Objekt einzeln zu behandeln, packen wir die Objekte in eine Auflistung, die serialisierbar ist – im Beispiel ArrayList. Da sie die Objekte in Feldern speichert, werden sie von der Serialisierung erfasst, und durch den rekursiven Charakter der Serialisierung werden deren Felder ebenfalls serialisiert. Wie das folgende Beispiel zeigt, tritt dann einfach die Auflistung an die Stelle eines Einzelobjekts. Das bedeutet: Es gibt nur einen Aufruf Serialize und nur einen Aufruf Deserialize, für egal wie viele Objekte.
Das Beispiel verwendet die Klassen ClassA und Person des Abschnitts 7.7.2, »Testklassen«. Erst wird eine Liste mit verschiedenen Objekttypen serialisiert. Dann folgt die Deserialisierung mit anschließender Testausgabe.
'...\IO\Serialisierung\Gemischt.vb |
Option Strict On Imports System.IO Imports System.Runtime.Serialization Imports System.Runtime.Serialization.Formatters.Binary Namespace EA Module Gemischt Sub Test() Dim path As String = IO.Path.GetTempFileName() Dim fs As FileStream ' Objekt und Formatierer erzeugen Dim liste As New ArrayList() liste.Add(New ClassA(2334, "Freddy")) liste.Add(New Person("Microsoft")) liste.Add(New ClassA(13, "Beate")) liste.Add(New Person("Tollsoft")) Dim bf As New BinaryFormatter() ' speichern fs = New FileStream(path, FileMode.Create) bf.Serialize(fs, liste) fs.Close() ' lesen fs = New FileStream(path, FileMode.Open) Dim liste2 As ArrayList Try liste2 = CType(bf.Deserialize(fs), ArrayList) Catch ex As SerializationException Console.WriteLine("Serialisierung: {0}", ex.Message) : Return Catch ex As IOException Console.WriteLine("Lesefehler: {0}", ex.Message) : Return Finally fs.Close() End Try ' Ausgabe For Each o As Object In liste2 If TypeOf o Is ClassA Then Dim c As ClassA = CType(o, ClassA) Console.WriteLine("{{intVar={0}, Name={1}}}", c.intVar, c.Name) Else Dim p As Person = CType(o, Person) Console.WriteLine("{{Name={0}}}", p.Name) End If Next File.Delete(path) Console.ReadLine() End Sub End Module End Namespace
Die Ausgabe bestätigt die korrekte Serialisierung:
{intVar=2334, Name=Freddy} {Name=Microsoft} {intVar=13, Name=Beate} {Name=Tollsoft}
Nichtserialisierte Daten
Alle Felder mit dem Attribut NonSerialized sind explizit von der Serialisierung ausgenommen. Zum Beispiel sollten Felder mit Kennwörtern nicht gespeichert werden:
<Serializable()> Class Login
Public Name As String
<NonSerialized()> Private Kennwort As String
...
End Class
Serialisierung und Vererbung
Das Serializable-Attribut wird nicht vererbt. Wenn Sie eine serialisierbare Klasse ClassA entwickeln und daraus die Klasse ClassB ableiten, muss diese selbst dann das Attribut haben, wenn nur die aus ClassA geerbten Mitglieder serialisiert werden sollen. Ansonsten ist die Subklasse nicht serialisierbar. Weiterhin ist eine Kindklasse nur serialisierbar, wenn auch die Elternklasse serialisierbar ist.
7.7.4 Serialisierung mit SoapFormatter
Die Serialisierung von Objektdaten mit SoapFormatter unterscheidet sich nicht von der mit BinaryFormatter. Allerdings muss zuerst eine weitere Bibliotheksdatei, wie in Tabelle 7.21, ».NET-Serialisierungsklassen« angegeben, unter Verweise eingetragen werden. Abbildung 7.5 zeigt eine typische Ausgabe, geöffnet im Internet Explorer.
Abbildung 7.5 Serialisierte Daten im SOAP-Format
7.7.5 Serialisierung mit XmlSerializer
Die Serialisierung mit der Klasse XmlSerializer aus dem Namensraum System.Xml.Serialization.XmlSerializer unterscheidet sich gravierend von den Klassen BinaryFormatter und SoapFormatter:
- Die im XML-Format zu serialisierende Klasse muss als Public definiert sein.
- Nur als Public deklarierte Instanzfelder werden serialisiert, klassengebundene sind ausgeschlossen.
- Die zu serialisierende Klasse muss einen öffentlichen, parameterlosen Konstruktor haben. Dieser wird von XmlSerializer aufgerufen.
- Die Steuerung der XML-Serialisierung erfolgt mit Attributen aus dem Namensraum System.Xml.Serialization.
- Das Serializable-Attribut ist nicht zwingend vorgeschrieben.
Obwohl die XML-Serialisierung aufwändiger zu programmieren ist, hat sie einen großen Vorteil: XML ist ein offener Standard und deshalb plattformunabhängig. Die auf diese Weise serialisierten Daten lassen sich folglich von »beliebigen« Anwendungen verarbeiten.
Im folgenden Beispiel wird das Beispiel aus Abschnitt 7.7.3, »Serialisierung mit BinaryFormatter«, so umgeschrieben, dass statt des BinaryFormatter ein XmlSerializer verwendet wird. Die Serialisierungsklassen ClassA und Person zeigt Abschnitt 7.7.2, »Testklassen«.
'...\IO\Serialisierung\Xml.vb |
Option Strict On Imports System.IO Imports System.Xml.Serialization Namespace EA Module Xml Sub Test() Dim path As String = IO.Path.GetTempFileName() Dim fs As FileStream ' Objekt und Formatierer erzeugen Dim bf As New XmlSerializer(GetType(ClassA)) Dim obj As New ClassA(310, "Peter") ' speichern fs = New FileStream(path, FileMode.Create) bf.Serialize(fs, obj) fs.Close() ' lesen fs = New FileStream(path, FileMode.Open) Dim obj2 As ClassA = CType(bf.Deserialize(fs), ClassA) fs.Close() ' Ausgabe Console.WriteLine("{{intVar={0}, Name={1}}}", obj2.intVar, obj2.Name) Console.WriteLine(File.ReadAllText(path)) File.Delete(path) Console.ReadLine() End Sub End Module End Namespace
Nur eine Zeile unterscheidet die binäre von der XML-Serialisierung:
Dim bf As New XmlSerializer(GetType(ClassA))
Der Inhalt der XML-Datei ist die zweite Ausgabe:
{intVar=310, Name=Peter} <?xml version="1.0"?> <ClassA xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <intVar>310</intVar> <Name>Peter</Name> </ClassA>
XML-Serialisierung mit Attributen steuern
Die Auswahl der serialisierten Daten und deren Ausgabeformat lassen sich mit Attributen steuern, die im Namensraum System.Xml.Serialization definiert sind. Tabelle 7.22 gibt einen kleinen Überblick über die wichtigsten Attribute.
Attribut | Beschreibung |
XmlArray |
Ein bestimmtes Klassenmitglied wird als Array serialisiert. |
XmlArrayItem |
Legt den XML-Bezeichner für den vom Array verwalteten Typ fest. |
XmlAttribute |
Die Eigenschaft wird als XML-Attribut statt als XML-Element serialisiert. |
XmlElement |
XML-Elementnamen, standardmäßig der Bezeichner des Feldes |
XmlIgnore |
Legt fest, dass die Eigenschaft nicht serialisiert werden soll. |
XmlRoot |
XML-Wurzelelement, standardmäßig der Name der serialisierten Klasse |
Das folgende Beispiel verwendet einige der Attribute. Hier sehen Sie zuerst die Definition der zu serialisierenden Klasse: eine Teilnehmerliste mit einzelnen Teilnehmern.
'...\IO\Serialisierung\XmlAttribute.vb |
Option Strict On Imports System.IO Imports System.Xml.Serialization Namespace EA <XmlRoot("PersonenListe")> Public Class Teilnehmerliste <XmlElement("Listenbezeichner")> Public Listenname As String <XmlArray("PersonenArray")> <XmlArrayItem("PersonObjekt")> _ Public Personen As Teilnehmer() Public Sub New() End Sub Public Sub New(ByVal name As String) Listenname = name End Sub End Class Public Class Teilnehmer <XmlElement("Name")> Public Zuname As String <XmlElement("Wohnort")> Public Ort As String <XmlElement("Alter")> Public Lebensalter As Integer <XmlAttribute("PersID")> Public ID As String Public Sub New() End Sub Public Sub New(zuname As String, ort As String, _ alter As Integer, id As String) Me.Zuname = zuname Me.Ort = ort Me.Lebensalter = alter Me.ID = id End Sub End Class ... End Namespace
Im Testcode erzeugen wir eine Teilnehmerliste mit zwei Teilnehmer-Einträgen, die wir wie üblich serialisieren und deserialisieren. Ansatt des XML-Dateiinhalts serialisieren wir das gerade deserialisierte Objekt in die Konsole.
'...\IO\Serialisierung\XmlAttribute.vb |
Option Strict On Imports System.IO Imports System.Xml.Serialization Namespace EA ... Module XmlAttribute Sub Test() Dim path As String = IO.Path.GetTempFileName() Dim fs As FileStream ' Objekt und Formatierer erzeugen Dim katalog As New Teilnehmerliste("Teilnehmerliste") Dim personen(1) As Teilnehmer personen(0) = New Teilnehmer("Peter", "Berlin", 45, "117") personen(1) = New Teilnehmer() personen(1).Zuname = "Franz-Josef" personen(1).Ort = "Aschaffenburg" katalog.Personen = personen Dim bf As New XmlSerializer(GetType(Teilnehmerliste)) ' speichern fs = New FileStream(path, FileMode.Create) bf.Serialize(fs, katalog) fs.Close() ' lesen fs = New FileStream(path, FileMode.Open) Dim katalog2 As Teilnehmerliste = _ CType(bf.Deserialize(fs), Teilnehmerliste) fs.Close() ' Ausgabe bf.Serialize(Console.Out, katalog2) 'Console.WriteLine(File.ReadAllText(path)) File.Delete(path) Console.ReadLine() End Sub End Module End Namespace
Bis auf das encoding-Attribut der ersten Zeile zeigt die Ausgabe den Inhalt der XML-Datei:
<?xml version="1.0" encoding="IBM437"?> <PersonenListe xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <Listenbezeichner>Teilnehmerliste</Listenbezeichner> <PersonenArray> <PersonObjekt PersID="117"> <Name>Peter</Name> <Wohnort>Berlin</Wohnort> <Alter>45</Alter> </PersonObjekt> <PersonObjekt> <Name>Franz-Josef</Name> <Wohnort>Aschaffenburg</Wohnort> <Alter>0</Alter> </PersonObjekt> </PersonenArray> </PersonenListe>
Beachten Sie, wie die Verwendung der Attribute die Elementbezeichner in der XML-Ausgabe ändert.
Ihre Meinung
Wie hat Ihnen das Openbook gefallen? Wir freuen uns immer über Ihre Rückmeldung. Schreiben Sie uns gerne Ihr Feedback als E-Mail an kommunikation@rheinwerk-verlag.de.