Vergangene Livecasts und Podcasts!

[21.10.2007] Datenzugriff mit SubSonic | Details | Download
[16.09.2007] ASP.NET Teil 3 - Enterprise Umgebungen | Details | Download
[02.09.2007] ASP.NET Teil 2 - Benutzerdefinierte Erweiterungen | Details | Download
[26.08.2007] Windows Live ID Development | Details | Download
[19.08.2007] ASP.NET Teil 1 - Basiswissen | Details | Download
Alle Livecasts (Insgesamt 9) | Alle Podcasts (Insgesamt 7)


 Wednesday, February 21, 2007

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
  • Binary
  • XML
  • Soap

1. Binäre Serialisierung


Die 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 Standardverhalten

using 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 IDeserializationCallback

Durch 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 Serialisierung


Eine 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 Serialisierungsereignisse


In 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-Serialisierung

Die XML-Serialisierung zählt nicht zu den .NET Standardserialisierungs-Verfahren und ist daher auch in einem anderen Namespace zu finden:
  • System.Xml.Serialization

2.1 Beispiel

Die 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-Attribute

Mit Hilfe von Attributen kann die XML Serialisierung gesteuert werden. Nachfolgend eine Übersicht der zur Verfügung stehenden Attribute.





3. Sonstiges

3.1.    Was passiert bei der Deserialisierung

Bei 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.


All comments require the approval of the site owner before being displayed.
Name
E-mail
Home page

Comment (HTML not allowed)  

Enter the code shown (prevents robots):