Serialization

Serializable Attribute

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. All fields in the class must also be serializable or must be marked with the non-serialized annotation.

[Serializable]
public class SomeClass {
  //mark members that are not serializable
  [NonSerialized] SomeNoneSerializedType t;
}

A formatter object is used to perform the serialization. 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);
      }
  }         
}

The formatter in this example is the binary formatter that is standard for .NET. Other formatters can be used, like a SOAP formatter. SOAP is an XML format, so it is readable and editable by humans. The SOAP formatter is not part of the standard DLLs, so a reference to its DLL must be added to the application that uses it.

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.

Serialization Callback

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: IDeserializationCallback {
  //mark members that are not serializable
  [NonSerialized] SomeNoneSerializedType t;

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

Serializable Interface

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)
 {
   SomeType t = (SomeType) info.GetValue("myType", typeof(SomeType));
 }

 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.