Serialization Part 2: Version-Tolerant Serialization

A prior article I wrote, titled “Serialization/Deserialization in .NET,” introduced serialization and touched on some of the basics. This article assumes you are familiar with the material that article covered. If you aren’t, refer to the prior article first.

As any good sequel should, this follow-up builds on the momentum of the first, yet delivers a different message. This article focuses on serialization and deserialization in the upcoming 2.0 release of the Microsoft .NET Framework, specifically version-tolerant serialization.

Version-Tolerant Serialization (VTS)

Versioning can mean a number of different things in software development, but commonly it is used synonymously with the term release and means the next iteration of a particular software product. If you think in terms of the objects used in your code, it could mean a change to a customer or some other object. Each time you release a new version of your software, whether it is an actual software product, a business application, or a pet application of your own, you have the potential to run into versioning issues if you’re using serialization within your application.

Serialization Example

To demonstrate the problem, the following example defines an object and then serializes it to a file. The object will be modeled after a couple of the fields in the Customer table in the sample Northwind database that comes with Microsoft SQL Server.

Here is a definition for the Customer, followed by code to serialize it to and then read it back from the file and display it:

using System.Runtime.Serialization;

[Serializable]
class Customer
{
   private string companyName = "";
   public string CompanyName
   {
      get { return this.companyName; }
      set { this.companyName = value; }
   }

   private string contactName = "";
   public string ContactName
   {
      get { return this.contactName; }
      set { this.contactName = value; }
   }

   public void Clear()
   {
      this.CompanyName = "";
      this.ContactName = "";
   }
}

using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Runtime.Serialization.Formatters;

class Program
{
   static void Main(string[] args)
   {
      Customer customer = new Customer();
      customer.CompanyName = "Alfreds Futterkiste";
      customer.ContactName = "Maria Anders";

      using (FileStream fileStream =
             new FileStream(@"C:\\mytest.bin", FileMode.Create))
      {
         BinaryFormatter formatter = new BinaryFormatter();
         formatter.Serialize(fileStream, customer);
      }
      customer.Clear();
      using (FileStream fileStream =
             new FileStream(@"C:\\mytest.bin", FileMode.Open))
      {
         BinaryFormatter formatter = new BinaryFormatter();
         customer = (Customer)formatter.Deserialize(fileStream);
      }
      Console.Write("{0} - {1}", customer.CompanyName,
                    customer.ContactName);
}

When you execute the code above, it creates an instance of your Customer, sets the name and company name properties, saves it to a file, and then reads it back in and displays it. It’s a rudimentary example that demonstrates serialization and deserialization and creates a binary data file. You could just as easily store your serialized object in a database rather than a file, but this example keeps it simple.

Now, check out what happens when you expand your Customer definition to include an additional ContactTitle property:

[Serializable]
class Customer
{
   private string companyName = "";
   public string CompanyName
   {
      get { return this.companyName; }
      set { this.companyName = value; }
   }

   private string contactName = "";
   public string ContactName
   {
      get { return this.contactName; }
      set { this.contactName = value; }
   }

   private string contactTitle = "";
   public string ContactTitle
   {
      get { return this.contactTitle; }
      set { this.contactTitle = value; }
   }

   public void Clear()
   {
      this.CompanyName = "";
      this.ContactName = "";
      this.ContactTitle = "";
   }
}

If you try to read the contents of that same file you saved out before, you’ll get a nasty outcome in the form of an exception (see Figure 1):

using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Runtime.Serialization.Formatters;

class Program
{
   static void Main(string[] args)
   {
      Customer customer = new Customer();

      using (FileStream fileStream =
             new FileStream(@"C:\\mytest.bin", FileMode.Open))
      {
         BinaryFormatter formatter = new BinaryFormatter();
         customer = (Customer)formatter.Deserialize(fileStream);
      }
      Console.Write("{0} - {1}", customer.CompanyName,
                    customer.ContactName);
}

Figure 1: SerializationException Is Thrown

The reason for this exception is that you “versioned” your Customer object. The previously saved binary file didn’t have all of the same properties as the new format, so deserializing the object from the file fails with an exception as depicted in Figure 1.

There are a couple of ways to work around the versioning problem:

  1. Take full control of the serialization/deserialization process by implementing the ISerializable interface.
  2. Use the VTS attributes to control the serialization/deserialization.

The ISerializable interface requires more code and control than you may have actually wanted. The middle of the road solution is to use the new VTS attributes.

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read