Facebook Button Twitter Button YouTube Button RSS Button

IT-Runde

BinaryFormatter: Serialisierung in C#

  • BinaryFormatter: Serialisierung in C#

    Als Entwickler steht man stets vor der Herausforderung alle relevante Daten seiner Anwendung zu speichern. Denn zu einem fehlerfreien Laufzeitverhalten gehört auch, dass alle Objekte in den gleichen Zustand wiederhergestellt werden, in dem sie, beim Verlassen einer Anwendung, waren. Man muss sie also vom Hauptspeicher persistent, z.B. auf eine Festplatte, sichern.

    Auf „normalen“ Speichermedien werden sog. „data streams“ (dt. „Datenströme“) gespeichert. So nennt man die kontinuierliche Abfolge von Datensätzen, deren Ende zu Beginn nicht festgelegt ist. Glücklicherweise unterstützt uns .NET bei dieser Art der Speicherung hervorragend.

    Das Stichwort ist die Serialisierung. Sie bietet die Möglichkeit, Objekte, sowie alle darauf referenzierten Objekte, in einen Datenstrom umzuwandeln und in einer Datei zu speichern. Im Folgenden beschäftigen wir uns mit dem BinaryFormatter, dieser wandelt unsere Objekt-Daten in einen binären Datenstrom um. Andere Formate, in die man Objekte serialisieren kann, wären beispielsweise XML oder SOAP. Das binäre Format bietet aber den Vorteil, dass wir keine zusätzliche Bibliothek einbinden müssen und es auch keine Probleme bei zirkulären Referenzen – zwei Klassen verweisen gegenseitig aufeinander – gibt.

    Automatische Serialisierung

    Die Automatische Serialisierung ist in .NET sehr einfach und es bedarf nur weniger Schritte. Zuerst müssen wir die entsprechende Klasse (vor dem Klassenanfang) als Serializible markieren. (Als Beispiel wurde eine einfache Klasse gewählt, die den Namen und das Alter einer Person speichert.)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    
    using System;
     
    [Serializable()]class Person
    {
       public string Name { get; private set; }
       public int Age { get; private set; }
       public Person(string name, int age)
       {
          Age = age;
          Name = name;
        }
    }

    Danach muss man noch beachten, dass der BinaryFormatter zum schreiben/lesen einer Datei einen FileStream benötigt. Daher ist es notwendig folgende Namespaces zu laden:

    1
    2
    3
    4
    
    //Enthält die Klasse "FileStream"
    using System.IO;
    //Enthält den "BinaryFormatter"
    using System.Runtime.Serialization.Formatters.Binary;

    Anschließend können Objekte der Klasse Person mit dem BinaryFormatter serialisert bzw. deserialisiert werden.

    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    
    void Example()
    {
        //Serialisierung
        Person pers1 = new Person("Schmidt", 36);
        //Neuer FileStream mit dem Pfad zur Datei und dem Modus "Create" wird erstellt.
        FileStream stream1 = new FileStream(@"D:\test.dat", FileMode.Create);
        BinaryFormatter formatter1 = new BinaryFormatter();
        formatter1.Serialize(stream1, pers1);
        stream1.Close();
     
        //Deserialisierung
        Person pers2;
        BinaryFormatter formatter2 = new BinaryFormatter();
        //Neuer FileStream mit dem Pfad zur Datei und dem Modus "Open" wird erstellt.
        FileStream stream2 = new FileStream(@"D:\test.dat", FileMode.Open);
        pers2 = (Person)formatter2.Deserialize(stream2);
    }

    PS: Der Dateiname und die Endung im FileStream (hier “test.dat”) können beliebig gewählt werden.

    Eine solche automatische Serialisierung ist sehr unkompliziert, da der BinaryFormatter für uns die Arbeit übernimmt. Manchmal ist es jedoch auch geschickt, eine eigene Serialisierung zu schreiben, beispielsweise aus Performance gründen oder weil die automatische Serialisierung bei einem bestimmten Objekttyp nicht erwartungsgemäß funktioniert.

    Benutzerdefinierte Serialisierung

    Um eine benutzerdefinierte Serialisierung (im Binär-Format) durchführen zu können, muss unsere Klasse Person erstmal von ISerializable (aus dem Namespace: System.Runtime.Serilization) erben.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    
    using System;
    using System.Runtime.Serialization;
     
    [Serializable()]
    class Person : ISerializable{
        public string Name { get; private set; }
        public int Age { get; private set; }
        public Person(string name, int age)
        {
            Age = age;
            Name = name;
        }
    }

    Als nächsten Schritt benötigt unsere Klasse die Methode GetObjectData, welche die Eigenschaften unseres Objektes serialisiert.

    public virtual void GetObjectData(SerializationInfo info, StreamingContext context)

    Da unsere Klasse Person nicht als sealed markiert ist – man kann also von ihr erben – ist es sinnvoll die GetObjectData-Methode auf virtual zu setzen. Dies erlaubt einer anderen Klasse welche von Person erbt die Methode zu überschreiben, damit sie auch andere Eigenschaften serialisieren kann.

    Da es durch das Serialisieren von Objekten möglich sein kann, die Eigenschaften (genauer die Objekt-Instanz-Daten) auszulesen, ist es sinnvoll folgende Sicherheitsberechtigung vor der GetObjectData-Methode festzulegen.

    //Festlegen der Sicherheitsberechtigung
    [System.Security.Permissions.SecurityPermission(System.Security.Permissions.SecurityAction.Demand, SerializationFormatter = true)]

    In den Standardeinstellungen führt sie dazu, dass nur lokaler Code diese Methode ausführen kann – also kein Code, der aus einem Intranet oder aus dem Internet heruntergeladen wurde.

    Wie oben gezeigt, hat GetObjectData zwei Parameter, mit denen es aufgerufen wird. In der Variable info vom Typ SerializationInfo müssen wir alle Eigenschaften unseres Objektes, welche wir serialisieren wollen, hinzufügen. Da wir nur einfache Datentypen (string, int) haben, müssen wir der Methode info.AddValue neben der jeweiligen Eigenschaft nur noch einen eindeutigen Namen (Identifier) übergeben.

    //info.AddValue(string name, object value)
    info.AddValue("name", Name);
    info.AddValue("age", Age);

    Zusammengesetzt sieht das dann so aus:

    24
    25
    26
    27
    28
    29
    30
    31
    
    //Festlegen der Sicherheitsberechtigung
    [System.Security.Permissions.SecurityPermission(System.Security.Permissions.SecurityAction.Demand, SerializationFormatter = true)]
     
    public virtual void GetObjectData(SerializationInfo info, StreamingContext context)
    {
        info.AddValue("name", Name);
        info.AddValue("age", Age);
    }

    Hinweis: werden mehrere Klassen ineinander verschachtelt, muss darauf geachtet werden, dass niemals bei zwei Eigenschaften der gleiche Identifier benutzt wird.

    Damit unsere Daten auch wieder deserialisiert werden können, müssen wir noch einen zweiten Konstruktor erstellen.

    protected Person(SerializationInfo info, StreamingContext context)

    Dieser ist für den Deserialisierungsvorgang nötig und sollte als Zugriffsebene protected haben, um eventuelle Sicherheitslücken auszuschließen. (Wenn die ganze Klasse als sealed markiert ist, kann man den Konstruktor auch ohne Probleme auf private setzen, da die Zugriffsebenen beim Deserialisierungsvorgang “ignoriert” werden.)

    Um die Daten beim Deserialisieren wieder in dem Objekt zu speichern, können wir sie aus der info-Variable auslesen:

    protected Person(SerializationInfo info, StreamingContext context)
    {
        Age = info.GetInt32("age");
        Name = info.GetString("name");
    }

    (Es müssen natürlich die gleichen Namen/Identifier benutzt werden die wir auch oben in der GetObjectData verwendet haben, um auch das richtige Element zurück zu bekommen.)

    Zusammenfassend könnte die Klasse also so aussehen:

    using System;
    using System.Runtime.Serialization;
     
    namespace BinaryFormatterTutorial
    {
        [Serializable()]
        class Person : ISerializable
        {
            public string Name { get; private set; }
            public int Age { get; private set; }
     
            public Person(string name, int age)
            {
                Age = age;
                Name = name;
            }
     
            protected Person(SerializationInfo info, StreamingContext context)
            {
                Age = info.GetInt32("age");
                Name = info.GetString("name");
            }
     
            //Festlegen der Sicherheitsberechtigung
            [System.Security.Permissions.SecurityPermission(System.Security.Permissions.SecurityAction.Demand, SerializationFormatter = true)]
     
            public virtual void GetObjectData(SerializationInfo info, StreamingContext context)
            {
                info.AddValue("name", Name);
                info.AddValue("age", Age);
            }
        }
    }

    Um nun ein Objekt unserer Klasse zu serialisieren, kann genau wie beim Automatischen Serialisieren in der Example-Methode gezeigt, der BinaryFormatter verwendet werden. Dieser benutzt jetzt automatisch unsere GetObjectData-Methode und den neuen Konstruktor zum serialisieren bzw. deserialisieren der Klassen-Instanzen.

    Wie gefällt euch dieses Tutorial? Wollt ihr mehr davon lesen? Uns interessiert eure Meinung! 😎

  • Kostenlose IT-Tipps

    • Nebenbei ein Online Business aufbauen
    • Passiv Geld generieren
    • Online Trends (Nischenseiten, SEO, Google & Co.)

     
     


  1. #1 Lenchen
    25.03.2013 um 23:51 Uhr

    Mir gefällt das Tutorial! Für User die genau danach suchen sicher sehr hilfreich, bin mir aber unsicher ob mehr als 50% der Stammleser damit etwas anfangen können. Haben sich ja noch nie damit gearbeitet oder auseinandergesetzt (Ohne Crashkurs für die breite Masse auch immer schwierig)…

    Post ReplyPost Reply
  2. #2 Francesco
    26.03.2013 um 13:46 Uhr

    Für den ersten hier ein schöner Artikel!

    Post ReplyPost Reply
Kommentar schreiben