Listing One

[Serializable]
public class SerializableFile 
   : ISerializable, IDisposable
{
   // Constructor only used for deserialization
   protected SerializableFile(
      SerializationInfo info, StreamingContext context)
   {
      OpenFile(
         info.GetString("name"), 
         info.GetInt64("position"));
   }
   // Method provides state for serialization
   public void GetObjectData(
      SerializationInfo info, StreamingContext context)
   {
      if (file != null)
      {
         info.AddValue("name", file.Name);
         info.AddValue("position", file.Position);
      }
   }

   private Stream file;
   // Constructor called by user code
   public SerializableFile(string name)
   {
      OpenFile(name, 0);
   }
   // Provide deterministic clean up
   public void Dispose()
   {
      if (file != null) file.Close();
      file = null;
   }
   // Other methods that use file

   // Helper method to initialise object
   private void OpenFile(string name, long position)
   {
      file = new FileStream(
         name, FileMode.OpenOrCreate, 
         FileAccess.ReadWrite, FileShare.ReadWrite);
      file.Position = position;
   }
}

Listing Two

FileStream stm = new FileStream(
   "data.soap", FileMode.OpenOrCreate, FileAccess.Write);
SoapFormatter sf = new SoapFormatter();
sf.Context = new StreamingContext(StreamingContextStates.File);
// obj is some serializable object
sf.Serialize(stm, obj);
stm.Close();

Listing Three

[Serializable]
class SerializedObject : ISerializable
{
   public void GetObjectData(
      SerializationInfo info, StreamingContext context)
   {
      switch(context.State)
      {
	case StreamingContextStates.CrossMachine:
         info.FullTypeName = "OtherMachineObject";
         break;
	case StreamingContextStates.Process:
         info.FullTypeName = "OtherProcessObject";
         break;
      // All other cases will be deserialized 
      // as SerializedObject
      }
      // Add values
   }
   // Other members
}

Listing Four

class MyBinder : SerializationBinder
{
   public override Type BindToType(
      string assemName, string typeName)
   {
      Type type = null;
      String myName = Assembly.GetExecutingAssembly().FullName;

      // If the serialized class is the first version, replace
      // it with the second version of the class. Note the 
      // preceding period in the class name.
      if (typeName == ".ClassV1" && assemName == myName)
      {
         type = Type.GetType(
            String.Format("ClassV2, {0}", myName));
      }
      else
      {
         type = Type.GetType(
            String.Format("{0}, {1}", typeName, assemName));
      }
      return type;
   }
}

Listing Five

FileStream stm = new FileStream(
   "data.soap", FileMode.Open, FileAccess.Read);
SoapFormatter sf = new SoapFormatter();
sf.Binder = new MyBinder();
ClassV2 o = (ClassV2)sf.Deserialize(stm);
stm.Close();
// Use the object

Listing Six

// Example of a serialization surrogate. This is a contrived 
// example used to indicate that surrogates are not a solution for
// every situation.
class FileSurrogate : ISerializationSurrogate
{
   // Platform Invoke access to the Win32 file API
   [DllImport("kernel32")]
   static extern int CreateFile(string name, uint access, 
      uint share, uint security, uint creation, uint attr, 
      uint template);
   const uint GENERIC_READWRITE = 0xc0000000;
   const uint FILE_SHARE_READWRITE = 3;
   const uint OPEN_ALWAYS = 4;
   // Open or create a file for read/write, shared for read
   // and write access
   static int CreateFile(string name)
   {
      return CreateFile(name, GENERIC_READWRITE, 
         FILE_SHARE_READWRITE, 0, OPEN_ALWAYS, 0, 0);
   }

   // Serialize the state of the object that is important for
   // recreating a FileStream object
   public void GetObjectData(object obj, SerializationInfo info,
      StreamingContext context)
   {
      FileStream fs = obj as FileStream;
      info.AddValue("name", fs.Name);
      info.AddValue("position", fs.Position);
      info.AddValue("canread", fs.CanRead);
      info.AddValue("canwrite", fs.CanWrite);
      info.AddValue("canseek", fs.CanSeek);
      // This is not accessible through a property, so use reflection
      FieldInfo fi = fs.GetType().GetField("_bufferSize", 
         BindingFlags.Instance|BindingFlags.NonPublic);
      info.AddValue("buffersize", (int)fi.GetValue(fs));
   }
   // Use the serialization stream to initialize an already 
   // created object
   public object SetObjectData(object obj, SerializationInfo info, 
      StreamingContext context, ISurrogateSelector selector)
   {
      FileStream fs = obj as FileStream;
      // Use Platform Invoke to open the file, note that we 
      // assume read and write access to the file is shared
      int ihandle = CreateFile(info.GetString("name"));
      IntPtr handle = new IntPtr(ihandle);
      // The handle is ref count protected in the FileStream object,
      // so create a __FileStreamHandleProtector class to do this
      Type type = fs.GetType().GetNestedType(
         "__FileStreamHandleProtector", 
         BindingFlags.Instance|BindingFlags.NonPublic);
      if (type != null)
      {
         // Now create the __FileStreamHandleProtector object
         // using the handle
         ConstructorInfo ci;
         ci = type.GetConstructor(
            BindingFlags.Instance |BindingFlags.NonPublic 
               |  BindingFlags.CreateInstance,
            null, new Type[]{typeof(IntPtr), typeof(Boolean)}, 
            null);
         object o = ci.Invoke(new object[]{handle, true});
         if (o != null)
         {
            // Initialize the FileStream._handleProtector in the
            // object we were given
            FieldInfo fihp = fs.GetType().GetField(
               "_handleProtector", 
               BindingFlags.Instance|BindingFlags.NonPublic);
            fihp.SetValue(fs, o);
         }
      }
      // Set the other FileStream fields
      FieldInfo fi = fs.GetType().GetField("_canRead", 
         BindingFlags.Instance|BindingFlags.NonPublic);
      fi.SetValue(fs, info.GetBoolean("canread"));
      fi = fs.GetType().GetField("_canWrite", 
         BindingFlags.Instance|BindingFlags.NonPublic);
      fi.SetValue(fs, info.GetBoolean("canwrite"));
      fi = fs.GetType().GetField("_canSeek", 
         BindingFlags.Instance|BindingFlags.NonPublic);
      fi.SetValue(fs, info.GetBoolean("canwrite"));
      fi = fs.GetType().GetField("_bufferSize", 
         BindingFlags.Instance|BindingFlags.NonPublic);
      fi.SetValue(fs, info.GetInt32("buffersize"));
      fi = fs.GetType().GetField("_fileName", 
         BindingFlags.Instance|BindingFlags.NonPublic);
      fi.SetValue(fs, info.GetString("name"));
      // This is the only field we can write through a property
      fs.Position = info.GetInt64("position");
      return null; // Formatters in the current versions of 
   }               // .NET ignore this return value.
}

Listing Seven

// Initialize the formatter's surrogate selector with our surrogate
SoapFormatter sf = new SoapFormatter();
SurrogateSelector ss = new SurrogateSelector();
ss.AddSurrogate(
   typeof(FileStream), 
   new StreamingContext(StreamingContextStates.All),
   new FileSurrogate());
sf.SurrogateSelector = ss;

// This is the object we will serialize
FileStream fs = new FileStream("data.txt", FileMode.OpenOrCreate, 
   FileAccess.ReadWrite, FileShare.ReadWrite);
// Write some data 
using (StreamWriter sw = new StreamWriter(fs))
{
   sw.WriteLine("first line");
   sw.WriteLine("second line");
   // We will serialize it to this stream
   using (FileStream stm = new FileStream("data.soap",
      FileMode.OpenOrCreate, FileAccess.Write))
   {
      // Serialize the object
      sf.Serialize(stm, fs);
   } // Note that stm is close here, this is important
} // The StreamWriter and FileStream are closed here

Listing Eight

// Every new exception class should be serializable because
// it could be passed across context boundaries
[Serializable]
class MyException : Exception, ISerializable
{
   // At a minimum, this constructor must be provided
   protected MyException(
      SerializationInfo info, StreamingContext context) 
      // The base class version should be called
      : base(info, context)
   {
      // If your exception has additional fields they should be
      // deserialized here
      data = info.GetString("Data");
   }
   // If your exception has additional fields you must override
   // this method and use it to serialize your fields
   public override void GetObjectData(
      SerializationInfo info, StreamingContext context)
   {
      info.AddValue("Data", data);
      // You must call the base class version
      base.GetObjectData(info, context);
   }

   // The following are members that this class adds
   public MyException(string message, string d) 
      : base(message)
   {
      data = d;
   }
   string data;
   public string Data {get{return data;}}
}

Listing Nine

public delegate void Del();

// This is the class that will be invoked. 
[Serializable]
public class X
{
   private string str;
   public X(string s)
   {
      str = s;
   }
   // The method that will be invoked through the serialized
   // delegate
   public void CallMe()
   {
      Console.WriteLine("you called {0}", str);
   }
}

Listing Ten

class App
{
   static void Main()
   {
      if (Environment.GetCommandLineArgs().Length == 1)
      {
         Usage();
         return;
      }

      if (Environment.GetCommandLineArgs()[1].Equals(
             "serial"))
      {
         SerializeDelegate();
      }
      else if (Environment.GetCommandLineArgs()[1].Equals(
                  "deserial"))
      {
         Deserialize();
      }
      else Usage();
   }
   static void Usage()
   {
      Console.WriteLine("Usage: SerDel <command>");
      Console.WriteLine("where <command> is serial or deserial");
   }
   // The user has requested that a delegate should be serialized
   static void SerializeDelegate()
   {
      Console.WriteLine("The time is {0}", DateTime.Now.ToString());
      Console.WriteLine("Serializing to data.xml...");

      // Store the time that the delegates were created
      string s;
      s = String.Concat("First object created ",
             DateTime.Now.ToString());
      X x1 = new X(s);
      Del d = new Del(x1.CallMe);
      Console.WriteLine(s);

      // Add a delay so that the times are suitably different
      Thread.Sleep(1000);

      // Add a second delegate to show that multicast delegates
      // can be serialized
      s = String.Concat("Second object created ",
             DateTime.Now.ToString());
      X x2 = new X(s);
      d += new Del(x2.CallMe);
      Console.WriteLine(s);

      // Serialize the delegate. Because I have used the SOAP
      // formatter, you can view this file to see the details
      // of how the delegate is serialized
      using (FileStream fs = File.Open("data.xml", FileMode.Create))
      {
         SoapFormatter sf = new SoapFormatter();
         sf.Serialize(fs, d);
      }
   }
   // The user has indicated that the stored delegate should be 
   // invoked. This will be a different process to the one that
   // created the delegate. Indeed, you can copy serdel.exe, 
   // objs.dll and data.xml to a different machine and invoke
   // the delegate there...
   static void Deserialize()
   {     
      Console.WriteLine("The time is {0}", DateTime.Now.ToString());
      Console.WriteLine("Deserializing...");
      using (FileStream fs = File.Open("data.xml", FileMode.Open))
      {
         SoapFormatter sf = new SoapFormatter();
         Del d = (Del)sf.Deserialize(fs);
         // Invoke the delegate
         d();
      }
   }
}

