Oft müssen Objekte zwischen unterschiedlichen Anwendungen (auch über Netzwerke hinweg) ausgetauscht oder einfach nur lokal persistiert (gespeichert) werden (um beispielsweise den Zustand eines Objektes zu speichern). Das Problem besteht nun darin, Objekte nicht ohne Weiteres transferiert werden können. Sie müssen dafür in eine spezielle Form gebracht werden. Dieser Vorgang nennt sich Serialisierung. Mögliche Formen (die durch das .NET Framework zur Verfügung gestellt werden) sind
1. Binäre SerialisierungDie binäre Serialisierung ist die Standardserialisierung im .NET Framework. Die Klasse BinaryFormatter stellt die Methoden Serializer() und Deserialize() zum Serialisieren bzw. Deserialisieren zur Verfügung.
1.1 Verwendete Namespaces in diesem Beispiel- System.IO
- System.Runtime.Serialization
- System.Runtime.Serialization.Formatters.Binary
1.2 Beispiel Standardverhaltenusing System;
using System.Collections.Generic;
using System.Text;
namespace SerializationDemo
{
[Serializable]
public class Person
{
#region Members
private string _firstname = null;
private string _lastname = null;
#endregion Members
#region Properties
public string Firstname
{
get { return this._firstname; }
set { this._firstname = value; }
}
public string Lastname
{
get { return this._lastname; }
set { this._lastname = value; }
}
#endregion Properties
}
}
Mit folgendem Code kann die Serialisierung nun vorgenommen werden:
FileStream fs = new FileStream("SerializedObjekt.Binary.bin", FileMode.Create);
BinaryFormatter bf = new BinaryFormatter();
Person person = new Person();
person.Firstname = "Norbert";
person.Lastname = "Eder";
bf.Serialize(fs, person);
fs.Close();
1.3 Beispiel IDeserializationCallbackDurch die Implementierung des Interfaces IDeserializationCallback ist es möglich, direkt nach der Deserialisierung Aktionen einzuleiten. Als Beispiel wäre die Berechnung eines Wertes denkbar, der aufgrund seiner Berechnung nicht serialisiert wurde, da dieser Wert nach der Serialisierung neu generiert werden kann. Dadurch kann zusätzlich der Serialisierungs-Output klein gehalten werden, was bei einer Netzwerkübertragung von Vorteil ist.
Für dieses Beispiel wird eine Klasse verwendet, die einen berechneten Wert enthält. Dieser wird durch das Attribute [NonSerialized()] nicht serialisiert. Zu beachten ist, dass dieses Attribut nur auf Feld-Ebene vergeben werden kann. Eigenschaften können damit nicht gekennzeichnet werden.
[Serializable]
public class CalculatedData : IDeserializationCallback
{
#region Members
private int _number1 = 0;
private int _number2 = 0;
[NonSerialized()] public int Total = 0;
#endregion Members
#region Properties
public int Number1
{
get { return this._number1; }
set { this._number1 = value; }
}
public int Number2
{
get { return this._number2; }
set { this._number2 = value; }
}
#endregion Properties
#region IDeserializationCallback Members
public void OnDeserialization(object sender)
{
this.Total = this._number1 + this._number2;
}
#endregion
}
Zu beachten ist die Implementierung des Interfaces IDeserializationCallback. Dadurch muss die Methode OnDeserialization implementiert werden. Diese wird unmittelbar nach der Deserialisierung aufgerufen und übernimmt in diesem Beispiel die Berechnung des öffentlichen Feldes Total.
MemoryStream ms = new MemoryStream();
BinaryFormatter bf = new BinaryFormatter();
CalculatedData cd = new CalculatedData();
cd.Number1 = 1;
cd.Number2 = 3;
bf.Serialize(ms, cd);
ms.Position = 0;
CalculatedData cd2 = (CalculatedData)bf.Deserialize(ms);
Console.WriteLine(String.Format("Total: {0}", cd2.Total));
Wird dieser Code ausgeführt, ist ersichtlich, dass der Wert Total erfolgreich berechnet wurde.
1.4 Beispiel einer benutzerdefinierten SerialisierungEine Serialisierung kann der Entwickler auch selbst in die Hand nehmen. Neben einer kompletten Eigenentwicklung besteht auch die Möglichkeit das Interface ISerializeable zu implementieren. Dadurch steht die Methode GetObjectData zur Verfügung, die als Parameter ein SerializationInfo-Objekt und ein StreamingContext-Objekt erhält.
Dem SerializationInfo-Objekt kann nun mittels der Methode AddValue ein Key-Value-Paar übergeben werden. Diese Informationen werden dann durch den verwendeten Formatter zur Serialisierung herangezogen.
Des weiteren muss ein Konstruktor mit den genannten Parametern erstellt werden. Dieser wird in weiterer Folge für die Deserialisierung verwendet. Ebenfalls zu achten ist, dass ein parameterloser Konstruktor zur Verfügung steht.
[Serializable]
public class CustomSerializationPerson : ISerializable
{
#region Members
private string _firstname = null;
private string _lastname = null;
#endregion
#region Properties
public string Firstname
{
get { return this._firstname; }
set { this._firstname = value; }
}
public string Lastname
{
get { return this._lastname; }
set { this._lastname = value; }
}
#endregion
#region ISerializable Members
public void GetObjectData(SerializationInfo info,
StreamingContext context)
{
info.AddValue("Firstname", this._firstname);
info.AddValue("Lastname", this._lastname);
}
public CustomSerializationPerson()
{
}
public CustomSerializationPerson(SerializationInfo info,
StreamingContext context)
{
this._firstname = info.GetString("Firstname");
this._lastname = info.GetString("Lastname");
}
#endregion
}
Diese Beispielklasse verfügt über alle notwendigen Methoden und Konstruktoren und kann daher zur Serialisierung verwendet werden:
MemoryStream ms = new MemoryStream();
BinaryFormatter bf = new BinaryFormatter();
CustomSerializationPerson csp = new CustomSerializationPerson();
csp.Firstname = "Norbert";
csp.Lastname = "Eder";
bf.Serialize(ms, csp);
ms.Position = 0;
CustomSerializationPerson cd2 = (CustomSerializationPerson)bf.Deserialize(ms);
Console.WriteLine(String.Format("Firstname: {0}, Lastname: {1}", csp.Firstname, csp.Lastname));
1.5 SerialisierungsereignisseIn der Standardserialisierung können insgesamt vier Ereignisse verwendet warden, um auf die Serialisierung bzw. auf die Deserialisierung zu reagieren.

Die Besonderheit dieser Ereignisse ist nun die, dass diese als Attribute gesetzt werden. Damit werden Methoden gekennzeichnet, die jeweils aufgerufen werden sollen. Zu beachten ist, dass diese Methoden als Rückgabetyp void besitzen müssen. Zusätzlich sind diese Ereignisse nur für die Standard-Serialisierung-Verfahren verfügbar.
Eine Erweiterung der Person-Klasse sieht wie folgt aus:
[Serializable]
public class Person
{
#region Members
private string _firstname = null;
private string _lastname = null;
#endregion Members
#region Properties
public string Firstname
{
get { return this._firstname; }
set { this._firstname = value; }
}
public string Lastname
{
get { return this._lastname; }
set { this._lastname = value; }
}
#endregion Properties
#region Private Methods
[OnDeserialized()]
private void OnDeserializedHandler(StreamingContext context)
{
if (this._firstname == null)
this._firstname = "[Not Given]";
if (this._lastname == null)
this._lastname = "[Not Given]";
}
#endregion
}
In diesem fall wird nach dem Deserialisierungsvorgang die Methode OnDeserializedHandler aufgerufen. In diesem werden die Member, wenn nicht gesetzt, auf [Not Given] gestellt. Wird nun bei der Serialisierung einer dieser Member nicht befüllt, kann das Ergebnis nach der Deserialisierung bewundert werden.
2. XML-SerialisierungDie XML-Serialisierung zählt nicht zu den .NET Standardserialisierungs-Verfahren und ist daher auch in einem anderen Namespace zu finden:
2.1 BeispielDie Verwendung dieses Serialisierungsverfahren funktioniert ähnlich zum BinaryFormatter. Gleich sind die angebotenen Methoden Serialize und Deserialize. Unter der Verwendung der bereits erwähnten Klasse Person sieht eine Serialisierung nun folgendermaßen aus:
System.IO.StringWriter sw = new StringWriter();
XmlSerializer xmlSer = new XmlSerializer(typeof(Person));
Person person = new Person();
person.Firstname = "Norbert";
person.Lastname = "Eder";
xmlSer.Serialize(sw, person);
StringBuilder sb = sw.GetStringBuilder();
Console.WriteLine(sb.ToString());
Die Ausgabe:
<?xml version="1.0" encoding="utf-16"?>
<Person xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Firstname>Norbert</Firstname>
<Lastname>Eder</Lastname>
</Person>
2.2 XML-Serialisierungs-AttributeMit Hilfe von Attributen kann die XML Serialisierung gesteuert werden. Nachfolgend eine Übersicht der zur Verfügung stehenden Attribute.
3. Sonstiges3.1. Was passiert bei der DeserialisierungBei der Deserialisierung werden die zu deserialisierenden Daten streng sequentiell abgearbeitet und das ursprüngliche Objekt hergestellt. Dieser Vorgang kann mitunter sehr komplex werden. Dies ist dann der Fall, wenn das serialisierte Objekt Referenzen auf andere Objekte hält, welche ebenfalls serialisiert wurden. Dafür verwendet das .NET Framework einen ObjektManager. Dieser hält fest, was bereits deserialisiert wurde und welche Bereiche noch ausstehen. Hierbei wird zwischen zwei Arten von Verweisen unterschieden:
- Rückwärtsverweis
- Vorwärtsverweis
Ein Rückwärtsverweis kommt zustande, wenn ein referenziertes Objekt bereits deserialisiert wurde. Dieses ist mit einem Fixup versehen und wird daher nicht nochmals deserialisiert. Bei einem Vorwärtsverweis wurde das referenzierte Objekt noch nicht deserialisiert. Ist dieser Verweis deserialisiert, wird er als abgeschlossen markiert.