Galileo Computing < openbook > Galileo Computing - Professionelle Bücher. Auch für Einsteiger.
Professionelle Bücher. Auch für Einsteiger.

Inhaltsverzeichnis
Vorwort zur 5. Auflage
1 Allgemeine Einführung in .NET
2 Grundlagen der Sprache C#
3 Klassendesign
4 Vererbung, Polymorphie und Interfaces
5 Delegates und Ereignisse
6 Weitere .NET-Datentypen
7 Weitere Möglichkeiten von C#
8 Auflistungsklassen (Collections)
9 Fehlerbehandlung und Debugging
10 LINQ to Objects
11 Multithreading und die Task Parallel Library (TPL)
12 Arbeiten mit Dateien und Streams
13 Binäre Serialisierung
14 Einige wichtige .NET-Klassen
15 Projektmanagement und Visual Studio 2010
16 XML
17 WPF – Die Grundlagen
18 WPF-Containerelemente
19 WPF-Steuerelemente
20 Konzepte der WPF
21 Datenbindung
22 2D-Grafik
23 ADO.NET – verbindungsorientierte Objekte
24 ADO.NET – Das Command-Objekt
25 ADO.NET – Der SqlDataAdapter
26 ADO.NET – Daten im lokalen Speicher
27 ADO.NET – Aktualisieren der Datenbank
28 Stark typisierte DataSets
29 LINQ to SQL
30 Weitergabe von Anwendungen
Stichwort

Buch bestellen
Ihre Meinung?

Spacer
<< zurück
Visual C# 2010 von Andreas Kühnel
Das umfassende Handbuch
Buch: Visual C# 2010

Visual C# 2010
geb., mit DVD
1295 S., 49,90 Euro
Rheinwerk Computing
ISBN 978-3-8362-1552-7
Pfeil 13 Binäre Serialisierung
Pfeil 13.1 Serialisierungsverfahren
Pfeil 13.2 Serialisierung mit »BinaryFormatter«


Galileo Computing - Zum Seitenanfang

13.2 Serialisierung mit »BinaryFormatter« topZur vorigen Überschrift

Ein Objekt unter .NET serialisierbar zu machen, ist geradezu genial einfach gelöst. Allerdings muss man sich schon bei der Entwicklung einer Klasse darüber im Klaren sein, dass Objekte der Klasse dem Serialisierungsprozess zugeführt werden sollen. Dazu wird die Klasse nur mit dem Attribut Serializable markiert:


[Serializable()] 
public class Person {...}

Fehlt das Attribut, wird die Ausnahme SerializationException ausgelöst. Alle Felder der Klasse Person, unabhängig davon, ob sie privat oder öffentlich deklariert sind, werden damit von der Serialisierung erfasst. Es gibt aber auch eine Einschränkung: Lokale Variablen und statische Klassendaten nehmen nicht an einem Serialisierungsprozess teil.

Wir wollen nun die Klassendefinition komplettieren, um anhand eines einfachen Beispiels zu sehen, wie die Serialisierung angestoßen und später das serialisierte Objekt rekonstruiert wird. Dazu implementieren wir in der Klasse Person ein privates und ein öffentliches Feld. Beide Felder werden über einen parametrisierten Konstruktor initialisiert.


[Serializable()] 
class Person {
  public string Name {get; set;}
  private int _Alter;
  // ----- Konstruktor -----
  public Person(int alter, string name) {
    _Alter = alter;
    Name = name;
  }
  public int Alter {
    get { return _Alter; }
  }
}

Bei der Serialisierung greift der Prozess den Inhalt von Alter und Name und speichert ihn entweder in einer Datei, im Netzwerk oder in einer Datenbank.

Der Code, der ein Objekt vom Typ der Klasse Person serialisiert, könnte folgendermaßen aussehen:


using System.Runtime.Serialization.Formatters.Binary;
...
Person pers = new Person(56, "Schmidt");
FileStream stream; 
stream = new FileStream(@"D:\MyPerson.dat", FileMode.Create);
BinaryFormatter formatter = new BinaryFormatter();
formatter.Serialize(stream, pers);
stream.Close();

Im Code wird ein FileStream generiert, der die binäre Datei MyPerson.dat anlegt oder, falls eine Datei dieses Namens bereits existiert, die alte überschreibt. Anschließend erzeugen wir ein Objekt vom Typ BinaryFormatter, dessen Methode Serialize wir aufrufen, wobei wir das Stream-Objekt und die zu serialisierende Objektreferenz übergeben.

Deserialisierung

Die Deserialisierung des gespeicherten Objekts ist genauso einfach. Beachtet werden muss dabei nur, dass der Rückgabewert vom Typ Object ist und deshalb noch in den richtigen Typ konvertiert werden muss:


Person pers;
BinaryFormatter formatter = new BinaryFormatter();
FileStream stream = new FileStream(@"D:\MyPerson.dat", FileMode.Open);
pers = (Person)formatter.Deserialize(stream);

Fassen wir nun den Code in einem Beispielprogramm zusammen. Serialisierung und Deserialisierung werden in je einer eigenen Methode behandelt, die aus Main heraus aufgerufen wird. Nach der Serialisierung des Objekts pers wird der neuen Objektvariablen oldPerson der Rückgabewert der Deserialisierung zugewiesen. Zum Schluss werden die rekonstruierten Objektdaten an der Konsole ausgegeben.


// ---------------------------------------------------------
// Beispiel: ...\Kapitel 13\BinaryFormatterSample
// ---------------------------------------------------------
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
class Program {
  static BinaryFormatter formatter;
  static FileStream stream;
  static void Main(string[] args) {
    formatter = new BinaryFormatter();
    Person pers = new Person(67, "Fischer");
    SerializeObject(pers);
    Person oldPerson = DeserializeObject();
    Console.WriteLine("Ergebnis der Deserialisierung:");
    Console.WriteLine(oldPerson.Alter);
    Console.WriteLine(oldPerson.Name);
  }
  // Objekt serialisieren
  public static void SerializeObject(Object obj) {
    stream = new FileStream(@"D:\MyObject.dat", FileMode.Create);
    formatter.Serialize(stream, obj);
    stream.Close();
  }
  // deserialisieren
  public static Person DeserializeObject() {
    FileStream stream = new FileStream(@"D:\MyObject.dat", FileMode.Open);
    return (Person)formatter.Deserialize(stream);
  }
}
// serialisierbare Klasse
 [Serializable()] class Person {
  private int _Alter;
  public string Name {get; set;}
  // ----- Konstruktor -----
  public Person(int alter, string name) {
    Name = name;
    _Alter = alter;
  }
  public int Alter {
    get { return _Alter; }
  }
}

Serialisierung mehrerer Objekte

Natürlich können mit einem Serialisierungsprozess auch beliebig viele, auch typunterschiedliche Objekte serialisiert werden. Dazu muss man für jedes Objekt Serialize auf demselben Stream-Objekt aufrufen. Die formatierten Daten werden entsprechend der Aufrufreihenfolge serialisiert.

Die Deserialisierung erfolgt in gleicher Weise: Es wird auf demselben Stream-Objekt so lange Deserialize aufgerufen, bis der Datenstrom versiegt. Das Lesen über das Ende des Datenstroms hinaus hat eine Ausnahme zur Folge. Dabei muss natürlich die Reihenfolge beachtet werden, in der die Objekte serialisiert worden sind, denn die Deserialisierung mehrerer Objekte folgt dem FIFO-Prinzip: Das zuerst serialisierte Objekt muss auch als Erstes wieder deserialisiert werden.

Für jedes einzelne Objekt Serialize aufzurufen, kann sehr arbeitsaufwendig sein. Außerdem muss die Anzahl der zu serialisierenden Objekte bekannt sein. Ist die Anzahl nicht vorhersehbar, muss ein anderer Weg beschritten werden. Es bietet sich dann an, alle Objekte in einer Objektauflistung zu verwalten und mit einem einzigen Serialize-Aufruf die gesamte Collection in den Datenstrom zu schreiben. Im folgenden Beispielprogramm wird das an vier Objekten unterschiedlichen Typs demonstriert. Sehen wir uns jedoch zuerst den Programmcode an.


// -------------------------------------------------------------
// Beispiel: ...\Kapitel 13\CollectionSerialization
// -------------------------------------------------------------
class Program {
  static void Main(string[] args) {
    ArrayList arrList = new ArrayList();
    Person pers1 = new Person("Fische", 23);
    Person pers2 = new Person("Beate", 38);
    Firma firma1 = new Firma("Tollsoft");
    Firma firma2 = new Firma("Microsoft");
    // Objekte einer ArrayList übergeben
    arrList.Add(pers1);
    arrList.Add(pers2);
    arrList.Add(firma1);
    arrList.Add(firma2); 
    // ArrayList-Objekt serialisieren
    SaveObject(arrList);
    ArrayList newArrList = new ArrayList();
    // Deserialisieren und die Objektdaten anzeigen
    GetObject(ref newArrList);
    foreach (object obj in newArrList) {
      if (obj is Person) {
        Console.Write("{0}/", ((Person)obj).Name);
        Console.WriteLine(((Person)obj).Alter);
      }
      else if (obj is Firma)
        Console.WriteLine(((Firma)obj).Name);
    }
    Console.ReadLine();
  }
  // ArrayList-Objekt speichern
  public static void SaveObject(ArrayList arr) {
    FileStream fs = new FileStream(@"D:\Objektliste.ifn", FileMode.Create);
    BinaryFormatter binFormatter = new BinaryFormatter();
    binFormatter.Serialize(fs, arr);
    fs.Close();
  }
  // Datei lesen und das ArrayList-Objekt in den Referenzparameter 
  // schreiben - der Rückgabewert gibt Aufschluss über den
  // Erfolg der Deserialisierung
  public static void GetObject(ref ArrayList arr) {
    FileStream fs = new FileStream(@"D:\Objektliste.ifn", FileMode.Open); ;
    try {
      BinaryFormatter formatter = new BinaryFormatter();
      arr = (ArrayList)formatter.Deserialize(fs);
    }
    catch (SerializationException e) {
      // die Datei kann nicht deserialisiert werden
      Console.WriteLine(e.Message);
    }
    catch (IOException e){
    // Beim Versuch, die Datei zu öffnen, ist ein Fehler aufgetreten.
    Console.WriteLine(e.Message);
    }
  }
}
// serialisierbare Klassen
 [Serializable()]
class Person {
  public string Name { get; set; }
  private int _Alter;
  // ----- Konstruktor -----
  public Person(string name, int alter) {
    Name = name;
    _Alter = alter;
  }
  // Eigenschaftsmethode
  public int Alter {
    get { return _Alter; }
  }
}
[Serializable()]
class Firma {
  public string Name { get; set; }
  //----- Konstruktor -----
  public Firma(string name) {
    Name = name;
  }
}

Als Typen sind die beiden Klassen Person und Firma mit je einem Konstruktor definiert, dem die Initialisierungswerte übergeben werden. Um sicherzustellen, dass Objekte der beiden Klassen serialisierbar sind, ist den Klassendefinitionen das Attribut Serializable angeheftet worden.

Der benutzerdefinierten Methode SaveObject kommt die Aufgabe der Objektserialisierung zu. Sie empfängt per Definition im Parameter arr die Referenz auf ein Objekt vom Typ ArrayList, die ebenfalls mit dem Attribut Serializable gekennzeichnet ist.

Die Entscheidung für eine Collection hat zwei Vorteile: Wir brauchen nicht jedes Mitgliedsobjekt der Auflistung einzeln zu serialisieren, sondern können mit einem einzigen Aufruf von Serialize unter Übergabe der ArrayList-Referenz automatisch jedes Objekt in den Datenstrom schreiben. Außerdem kann diese Collection unterschiedliche Typen verwalten.


formatter.Serialize(fs, arr);

GetObject liefert die Referenz auf das deserialisierte ArrayList-Objekt über den Parameter arr an den Aufrufer zurück, der seinerseits in der Pflicht steht, die Daten der einzelnen Listenobjekte abzufragen.

Beachten Sie, dass nur ein Deserialize-Aufruf notwendig ist, um die Daten aller von der Collection verwalteten Objekte wieder zu erhalten. Der Rückgabewert vom Typ object muss nur noch in ArrayList konvertiert werden.


arr = (ArrayList)formatter.Deserialize(fs);

Um SaveObject und GetObject zu testen, erzeugen wir in der Routine Main insgesamt vier Objekte – jeweils zwei von Person und Firma. Diese Objekte werden anschließend zu Elementen des ArrayList-Objekts arrList.

Um uns vom Erfolg zu überzeugen, wird ein zweites Objekt vom Typ ArrayList erzeugt, dem die deserialisierten Objekte zugewiesen werden. Die Auflistung wird in einer foreach-Schleife Element für Element durchlaufen, und der Typ der aktuellen Referenz wird mit dem is-Operator ermittelt. Bei der Ausgabe ist die explizite Konvertierung in den entsprechenden Typ zu berücksichtigen.

Daten als nichtserialisierbar kennzeichnen

Mit dem Attribut Serializable werden alle Instanzfelder einer Klasse serialisiert. Das mag im Einzelfall aber nicht immer wünschenswert sein. Eigenschaften, die der Serialisierungsprozess nicht erfassen soll, können durch das Setzen des Attributs NonSerialized vor der Deklaration ausgeschlossen werden.


[Serializable()] 
class Person {
  public string Name {get; set;} 
  // Variable Alter wird nicht serialisiert
  [NonSerialized()]
   private int _Alter; 
  // Anweisungen
}

Das Codefragment enthält die Klassendefinition der Felder Name und Alter. Beide würden normalerweise während der Serialisierung abgegriffen. Die Deklaration der privaten Instanzvariablen Alter ist allerdings als NonSerialized markiert und entzieht sich dem Serialisierungsprozess.

Serialisierung in einer abgeleiteten Klasse

Das Serializable-Attribut wird nicht vererbt. Wenn Sie also eine serialisierbare Klasse DemoA entwickeln und daraus die Klasse DemoB ableiten, muss das Attribut selbst dann mit DemoB verknüpft werden, wenn nur die aus DemoA geerbten Mitglieder serialisiert werden sollen. Ansonsten gilt die Subklasse als nicht serialisierbar.

Sollen die Felder der Klasse DemoB, die aus DemoA abgeleitet ist, serialisiert werden, muss auch die Basisklasse mit dem Serializable-Attribut verknüpft sein.



Ihr Kommentar

Wie hat Ihnen das <openbook> gefallen? Wir freuen uns immer über Ihre freundlichen und kritischen Rückmeldungen. >> Zum Feedback-Formular
<< zurück
  Zum Katalog
Zum Katalog: Visual C# 2010

Visual C# 2010
Jetzt bestellen


 Ihre Meinung?
Wie hat Ihnen das <openbook> gefallen?
Ihre Meinung

 Buchempfehlungen
Zum Katalog: Professionell entwickeln mit Visual C# 2012






 Professionell
 entwickeln mit
 Visual C# 2012


Zum Katalog: Windows Presentation Foundation






 Windows Presentation
 Foundation


Zum Katalog: Schrödinger programmiert C++






 Schrödinger
 programmiert C++


Zum Katalog: C++ Handbuch






 C++ Handbuch


Zum Katalog: C/C++






 C/C++


 Shopping
Versandkostenfrei bestellen in Deutschland und Österreich
InfoInfo




Copyright © Rheinwerk Verlag GmbH 2010
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das <openbook> denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.


Nutzungsbestimmungen | Datenschutz | Impressum

Rheinwerk Verlag GmbH, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, service@rheinwerk-verlag.de

Cookie-Einstellungen ändern