© 2004 Microsoft Corporation. All rights reserved.

Figure 1 Serialization and Deserialization
[Serializable]
public class Hashtable : ISerializable, IDeserializationCallback {
   // Hashtable's internal fields go here (not shown)

   SerializationInfo _savedSI;  // Only used for deserialization

   // Method to control Hashtable serialization
   // I recommend using an Explicit Interface Method Impl. here
   void ISerializable.GetObjectData(
      SerializationInfo info, StreamingContext context) {

      // Call info.AddValue to manually add most fields of the
      // Hashtable object to the serialization stream (not shown)

      // Create Object arrays for the Hashtable's key/value objects
      Object[] keys  = new Object[countOfEntries];
      Object[] values = new Object[countOfEntries];

      // Fill the arrays with references to the Hashtable's 
      // key/value objects (not shown)

      // Add the key/value objects to the serialization stream
      info.AddValue("Keys",   keys);
      info.AddValue("Values", values);
   }

   // Special constructor (required by ISerializable) to 
   // control Hashtable deserialization
   protected Hashtable(
      SerializationInfo info, StreamingContext context) {

       // Save the serialization stream info in a member. 
       // We want to postpone populating the Hashtable object until the
       // key/value objects have been completely deserialized since
       // the fields in the key objects are likely to affect
       // the hash code value for these key objects
      _savedSI = info;
   }

   // Method called after all key/value objects have been deserialized 
   // I recommend using an Explicit Interface Method Impl. here
   void IDeserializationCallback.OnDeserialization(Object sender) {

      // All objects in the stream have been deserialized.
      // We can now populate the Hashtable object. 

      // Call _savedSI.GetValue to manually initialize most fields of
      // the Hashtable object from the serialization stream (not shown)

      // Get the key/value objects from the serialization stream
      Object[] keys   = _savedSI.GetValue("Keys");
      Object[] values = _savedSI.GetValue("Values");

      // Add each key/value pair to the Hashtable
      for (Int32 x = 0; x < keys.Length; x++) {
         Add(keys[x], values[x]);
      }

      // Set this field to null so that the SerializationInfo object
      // can be reclaimed by the garbage collector
      _savedSI = null;
   }
}
Figure 2 ISerializable
using System;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters;
using System.Runtime.Serialization.Formatters.Binary;
using System.Runtime.Serialization.Formatters.Soap;
using System.Reflection;

///////////////////////////////////////////////////////////////////////

[Serializable]
class Person {
   private String title;
   
   public Person(String title) {
      this.title = title;
   }

   public override String ToString() { 
      return String.Format("{0}", title);
   }
}

///////////////////////////////////////////////////////////////////////

[Serializable]
class Employee : Person {
   private String title;

   public Employee(String title) : base("Person") {
      this.title = title;
   }
   
   public override String ToString() { 
      return String.Format("{0} -> {1}", title, base.ToString());
   }
}

///////////////////////////////////////////////////////////////////////

[Serializable]
class Manager : Employee, ISerializable {
   private String title;

   public Manager() : base("Employee") {
      this.title = "Manager";
   }

   void ISerializable.GetObjectData(
      SerializationInfo info, StreamingContext context) {

      // Serialize the desired values for this class
      info.AddValue("title", title);

      // Get the set of serializable members for our class and base classes
      Type thisType = this.GetType();
      MemberInfo[] mi = 
         FormatterServices.GetSerializableMembers(thisType, context);

      // Serialize the base class's fields to the info object
      for (Int32 i = 0 ; i < mi.Length; i++) {
         // Don't serialize fields for this class
         if (mi[i].DeclaringType == thisType) continue;
         info.AddValue(mi[i].Name, ((FieldInfo) mi[i]).GetValue(this));
      }
   }

   protected Manager(SerializationInfo info, StreamingContext context) :
      base(null) {

      // Get the set of serializable members for our class and base classes
      Type thisType = this.GetType();
      MemberInfo[] mi = FormatterServices.GetSerializableMembers(
         thisType, context);

      // Deserialize the base class's fields from the info object
      for (Int32 i = 0 ; i < mi.Length; i++) {
         // Don't deserialize fields for this class
         if (mi[i].DeclaringType == thisType) continue;

         // To ease coding, treat the member as a FieldInfo object
         FieldInfo fi = (FieldInfo) mi[i];

         // Set the field to the deserialized value
         fi.SetValue(this, info.GetValue(fi.Name, fi.FieldType));
      }

      // Deserialize the values that were serialized for this class
      title = info.GetString("title");
   }

   public override String ToString() { 
      return String.Format("{0} -> {1}", title, base.ToString());
   }
}

///////////////////////////////////////////////////////////////////////

class App {
   public static void Main() {
      FileStream stream = new FileStream(@"Data.dat", FileMode.Create);
      IFormatter formatter = new SoapFormatter();
      Manager m = new Manager();
      Console.WriteLine(m.ToString());
      formatter.Serialize(stream, m);
      stream.Close();

      stream = new FileStream(@"Data.dat", FileMode.Open);
      m = (Manager) formatter.Deserialize(stream);
      stream.Close();
      Console.WriteLine(m.ToString());
   }
}
Figure 3 StreamingContext Properties

Member Name
Member Type
Description
State
StreamingContextStates
A set of bit-flags indicating the source or destination of the objects being serialized/deserialized
Context
Object
A reference to an object that contains any user-desired context information
Figure 4 Bit Flag Values

Flag Name
Flag Value
Description
CrossProcess
0x0001
Source or destination is a different process on the same machine
CrossMachines
0x0002
Source or destination is on different machine
File
0x0004
Source or destination is a file. Don't assume that same process will deserialize the data
Persistence
0x0008
Source or destination is a store such as a database, file, or other. Don't assume that same process will deserialize the data
Remoting
0x0010
Source or destination is remoting to an unknown location. The location may be on the same machine but may also be on another machine
Other
0x0020
Source or destination is unknown
Clone
0x0040
Object graph is being cloned. The serialization code may assume that same process will deserialize the data and it is therefore safe to access handles or other unmanaged resources
CrossAppDomain
0x0080
Source or destination is a different AppDomain
All
0x00FF
Source or destination may be any of the above contexts. This is the default context
Figure 5 Serialize and Deserialize a Singleton
// There should be only one instance of this type per AppDomain
[Serializable]


public sealed class Singleton : ISerializable {
   // This is the one instance of this type
   private static readonly Singleton theOneObject = new Singleton();

   // Here are the instance fields
   public String someString;
   public Int32 someNumber;

   // Private constructor allowing this type to construct the singleton
   private Singleton() { 
      // Do whatever is necessary to initialize the singleton
      someString = "This is a string field";
      someNumber = 123;
   }

   // Method returning a reference to the singleton
   public static Singleton GetSingleton() { 
      return theOneObject; 
   }

   // Method called when serializing a Singleton
   // I recommend using an Explicit Interface Method Impl. here
   void ISerializable.GetObjectData(
      SerializationInfo info, StreamingContext context) {

      info.SetType(typeof(SingletonSerializationHelper));
      // No other values need to be added
   }

   // NOTE: The special constructor is NOT necessary because 
   // it's never called
}

[Serializable]
internal sealed class SingletonSerializationHelper : IObjectReference {
   // This object has no fields (although it could)

   // Method called after this object is deserialized
   public Object GetRealObject(StreamingContext context) {
      return Singleton.GetSingleton();
   }
}
Figure 6 Verifying Singleton Code
static void SingletonSerializationTest(Stream stream) {
   // Create an array with multiple elements refering to 
   // the one Singleton object
   Singleton[] a1 = { 
      Singleton.GetSingleton(), 
      Singleton.GetSingleton() 
   };

   Console.WriteLine(
      "Do both array elements refer to the same object? " + 
      (a1[0] == a1[1]));     // Displays "True"

   // Serialize the array elements
   formatter.Serialize(stream, a1);

   // Deserialize the array elements
   stream.Position = 0;
   Singleton[] a2 = (Singleton[]) formatter.Deserialize(stream);


   Console.WriteLine(
      "Do both array elements refer to the same object? " + 
      (a2[0] == a2[1]));     // Displays "True"

   Console.WriteLine(
      "Do all  array elements refer to the same object? " + 
      (a1[0] == a2[0]));     // Displays "True"
}