Tuesday, March 3, 2009

Serialization

There are several ways to implement serialization in .NET. The simplest way is to mark the class as Serializable and then to create a formatter to do the serialization. Fields that are not serializable can be marked as such and will not be serialized.
[Serializable]
public class SomeClass {
//mark members that are not serializable
[NonSerialized] SomeNoneSerializedType t;
}
The formatter can be created in any class that needs to serialize the above class.
public void SomeSaveHandler(Object sender, EventArgs e) {

using (SaveFileDialog dlg = new SaveFileDialog())
{
if (dlg.ShowDialog() != DialogResult.OK) return;
using (Stream stream =
new FileStream(dlg.FileName, FileMode.Create, FileAccess.Write))
{
IFormatter formatter = new BinaryFormatter();
formatter.Serialize(stream, instanceSomeClass);
}
}
}
Deserialization is similar.
public void SomeOpenHandler(Object sender, EventArgs e) {

using (OpenFileDialog dlg = new OpenFileDialog())
{
if (dlg.ShowDialog() != DialogResult.OK) return;
using (Stream stream =
new FileStream(dlg.FileName, FileMode.Open, FileAccess.Read))
{
IFormatter formatter = new BinaryFormatter();
SomeClass someClass = (SomeClass) formatter.Deserialize(stream);
}
}
}
More than one object can be serialized by having additional calls to serialize; one for each object. During deserialization, make a corresponding call to deserialize for each object that was serialized; use the same order when deserializing as was used when serializing.

For elements that are calculated fields, they can be recalculated after the object has been deserialized, by implementing the IDeserializationCallback interface. Define the callback method OnDeserialization to instantiate any calculated fields. The parameter to this method is not implemented at this time, so it is always null.
[Serializable]
public class SomeClass: IDerializationCallback {
//mark members that are not serializable
[NonSerialized] SomeNoneSerializedType t;

public void OnDeserialization(object sender) {
t = SomeCalculation();
}
}


The other method to serialize takes a different approach. Instead of assuming that everything is serializable, it assumes that nothing is serializable. Instead of indicating what should not be serialized, the class must specifically serialize each element. This technique is implemented in the interface ISerializable. Define the method GetObjectData and define a constructor that will be used during deserialization.
[Serializable]
public class SomeClass : ISerializable {
SomeType t;
SomeNoneSerializedType non;

public SomeClass(
SerializationInfo info, StreamingContext context)
{
t = (SomeType) info.GetValue("myType", typeof(SomeType));
non = SomeCalculation();
}

public void GetObjectData(
SerializationInfo info, StreamingContext context)
{
info.AddValue("myType", t);
}
}
Call info.AddValue for every member that needs to be serialized. Call info.GetValue for every member that needs to be deserialized.

There is no need for the deserialization callback interface, since calculated fields can be instantiated in the deserialization constructor.

This technique is useful for classes that extend a non-serializable class. The first technique will fail, if the base class cannot be serialized. By using the second technique and specifically serializing each element, the base class does not need to be serializable. The trick in this case is to make sure that any other constructors also get called when the deserialization constructor is called.

For the class that has to serialize this class, there is no difference when creating a formatter for either of these techniques. The calling class just calls serialize and deserialize on the formatter, it is up to the serializable class to determine how it will serialize.



It is possible to add methods to a class that will serialize/deserialize the class by creating a formatter. This encapsulates the entire process of serializing the class so that the class that is doing the serialization will not have to implement the details for creating the formatter.

The second technique is the preferred technique for implementing such methods. While the first technique could be used, its simplicity is lost, since it is not possible to deserialize into this. In order to make the first technique work from a method in the class, it would be necessary to deserialize into another class and then copy member by member into the current class. Another possible implementation would be to serialize/deserialize member by member.

No comments:

Post a Comment

Followers