Serialisierung

Aus Das Sopra Wiki
Wechseln zu: Navigation, Suche

Serialisierung[1] bezeichnet den Vorgang, ein Objekt in einen Datenstrom umzuwandeln. Dieser kann dann als auf einer Festplatte gespeichert oder über ein Netzwerk übertragen werden. C# stellt dafür Klassen bereit.

Formate

Es gibt mehrere Möglichkeiten, ein Objekt zu serialisieren:

Binary Serialization
Das Objekt wird in einem Binärformat gespeichert. Es kann nur von .NET gelesen werden.
XML Serialization
Das Objekt wird in ein XML-Format serialisiert. Es kann dadurch relativ einfach auch auf anderen Plattformen gelesen werden. Ausserdem kann man die Dateien als Mensch intuitiv verstehen und sogar editieren.
SOAP
Stadardisiertes Verfahren um Objekte zu serialisieren. Dadurch extrem einfach für andere Plattformen aber auch etwas schwerer für den Menschen zu lesen.

Was kann Serialisiert werden

Um Ein Objekt zu serialisieren braucht es extrem wenig Code. Man sollte allerdings beachten, daß nur sinnvolle Daten serialisiert werden. Instanzen der Klasse File können zum Beispiel nicht serialisiert werden. Der Grund ist, dass deren Inhalte nicht im Hauptspeicher liegen. Ebenfalls können einige XNA-Klassen nicht serialisiert werden, z.B. die Model-Klasse, weil Teile davon im Grafikkartenspeicher oder anderen Orten liegen. Wenn man nicht sicher ist, kann man sich mit F12 (oder Rechtsklick -> Go to Defninition) zum Code wechseln(oder zu den Metadaten). Und an den Attributen erkennen, ob sie serialisierbar sind und welche Felder ausgeschlossen werden. Der XmlSerializer hat weitere Einschränkungen:

  • Eine Klasse die Xml-serialisiert werden soll, braucht einen parameterlosen Konstrukor.
  • keine Zweidimensionale Arrays
  • (Die Klasse Dictionary und einige andere Klassen, von denen man es eigentlich nicht denkt, sind nicht erlaubt)

Attribute

Klassen und Felder können Attribute erhalten, ob sie serialisert werden sollen oder nicht. Wenn sie serialisiert werden sollen erhalten sie folgendes Attribut:

[Serializable]

In der Standardeinstellung werden alle Felder und Properties dieser Klasse serialisiert, auch die privaten. Um das zu verhindern kann man diese mit folgendem Attribut markieren:

[NonSerialized]

Man muss allerdings beachten, daß XmlSerializer keine privaten Felder und keine private Properties serialisiert, sondern nur, was als public markiert ist.

Ein kleines Beispiel:

[Serializable]
public class GameUnit
{
  public int hitPoints;
  public Vector3 position;
  private UnitAi unitAi;
  [NonSerialized]
  public Model unitModel;
}
[Serializable]
public class UnitAi
{
  public StateMachineState state;
  private int abc;
}

Wenn eine Instanz dieser Klasse serialisiert und gespeichert wird, werden folgende Daten gespeichert:

  • Beim BinaryFormatter
    • Der Integer hitPoints
    • Das Strukt position mit seinen Feldern
    • Die Referenz unitAi und die Instanz auf die sie zeigt. Und entsprechend dann auch deren Felder state und abc.
  • Beim XmlSerializer
    • Der Integer hitPoints
    • Das Strukt position mit seinen Feldern
    • Die Referenz unitAi wird nicht serialisiert, da sie als private deklariert ist

Referenzen

Referenzen werden in XMlSerializer und BinaryFormatter ein bisschen unterschiedlich behandelt. Da es in Xml keine Referenzen gibt, werden an derer Stelle einfach die Objekte eingeschachtelt. Während der BinaryFormatter dafür sorgt, dass nach dem Deserialisieren wieder alles so ist, wie vor dem Serialisieren. Es kann vorkommen, dass es im ganzen Serialisierungsvorgang eine Instanz mehrfach referenziert wird. Diese Instanz wird vom BinaryFormatter nur einmal serialisiert und beim Deserialisieren werden alle Referenzen wieder hergestellt. Auf diese Weise kann man eine komplette Objekthierarchie speichern. Nach dem Xml-Serialisierung und -Deserialisieren hat man jedoch für jede Referenz eine eigene Instanz, damit muss man vorsichtig sein.

BinaryFormatter

Man binde folgendes ein:

using System.IO;
using System.Runtime.Serialization.Formatters.Binary;

Zum Speichern einer Instanz wird folgender Code verwendet:

void SaveUnit(GameUnit gameUnit, String path)
{
  FileStream fs = null;
  try
  {
    fs = new FileStream(path, System.IO.FileMode.Create);
    BinaryFormatter bf = new BinaryFormatter();
    bf.Serialize(fs, gameUnit);
  }
  catch (Exception e)
  {
    // TODO: Fehlerbehandlung hier
  }
  finally
  {
    // Immer den Stream schliessen
    if (fs != null)
    {
      fs.Close();
    }
  }
}

Zum Laden einer Instanz wird folgender Code verwendet:

GameUnit LoadUnit(String path)
{
  FileStream fs = null;
  try
  {
    fs = new FileStream(path, FileMode.Open);
    BinaryFormatter bf = new BinaryFormatter();
                  
    return (GameUnit)bf.Deserialize(fs); 
  }
  catch (Exception e)
  {
    // TODO: Fehlerbehandlung hier
    return null;
  }
  finally
  {
    if (fs != null)
    {
      fs.Close();
    }
  }
}

XmlSerializer

Man binde folgendes ein:

using System.IO;
using System.Xml.Serialization;

Um ein Objekt dann in eine Xml-Datei zu speichern führt man folgenden Code aus:

void SaveUnit(GameUnit gameUnit, String path)
{
  FileStream fs = null;
  try
  {
    fs = new FileStream(path, System.IO.FileMode.Create);
    XmlSerializer seri = new XmlSerializer(typeof(GameUnit));
    seri.Serialize(fs, gameUnit);
  }
  catch (Exception e)
  {
    // TODO: Fehlerbehandlung hier
  }
  finally
  {
    // Immer den Stream schliessen
    if (fs != null)
    {
      fs.Close();
    }
  }
}

Um das Objekt wieder einzulesen benutzt man folgende Zeilen:

GameUnit LoadUnit(String path)
{
  FileStream fs = null;
  try
  {
    fs = new FileStream(path, FileMode.Open);
    XmlSerializer seri = new XmlSerializer(typeof(GameUnit));                
    return (GameUnit)seri.Deserialize(fs); 
  }
  catch (Exception e)
  {
    // TODO: Fehlerbehandlung hier
    return null;
  }
  finally
  {
    if (fs != null)
    {
      fs.Close();
    }
  }
}


Man muss dabei beachten, daß die Klasse BinaryFormatter[2] wesentlich mehr kann als die Klasse XmlSerializer[3], die dafür auch auf der XBox 360 zur Verfügung steht.

Netzwerk

Man kann mit Serialisierung auch Daten über ein Netzwerk schicken. Das Prinzip ist das gleiche, da die Formatter auf Streams arbeiten. Was für einen Stream man nimmt ist ihnen egal. Für Netzwerkfunktionalität bieten sich entweder die speziellen XNA-Klassen dafür an, oder man schreibt die Funktionalität selbst. Dies ist flexibler und man braucht keinen XBox-Live-Account. Dafür funktioniert sie auf der XBox nicht.

Die nötigen Klassen finden sich im Namespace

System.Net.Sockets

Hier ein kleines Beispiel für einen Server, der einen String entgegen nimmt.

TcpListener listener = new TcpListener(11111);
listener.Start();
using (var client = listener.AcceptTcpClient())
{
	Stream s = client.GetStream();
	BinaryFormatter bf = new BinaryFormatter();
	String str = (String)bf.Deserialize(s);
	Console.WriteLine(str);
	Console.ReadLine();
}
listener.Stop();

und hier der passende Client

using (TcpClient client = new TcpClient("localhost", 11111))
{
	Stream s = client.GetStream();
	BinaryFormatter bf = new BinaryFormatter();
	bf.Serialize(s, Console.ReadLine());
}
Console.ReadLine();

XML

Für XML kann man sich folgende Klassen anschauen

  • IntermediateSerializer [1] und [2]
  • XmlSerializer [3]
  • Leistungsfähiger als die beiden oben ist der SoapFormatter

Video Tutorial

Auf MSDN gibt es auch einen Webcast zum Thema[4]

Referenzen