ENTERPRISE

Programming .NET Components : Serialization Events (part 3) - Type-Version Tolerance

5/8/2013 9:22:03 PM

4. Type-Version Tolerance

In .NET 1.1, there had to be absolute compatibility between the metadata used to serialize a type and the metadata used to deserialize a type. This meant that if your application had clients with the serialized state of your types, your type members' metadata had to be immutable, or you would break those clients.

In .NET 2.0, the formatters acquired some version-tolerance capabilities. The tolerance is with respect to changes in the type metadata, not changes to the assembly version itself. Imagine a class-library vendor that provides a serializable component. The various client applications are responsible for managing the serialization medium (typically a file). Suppose the vendor changes the component definition, by adding a member variable. Such a change does not necessitate a version change, because binary compatibility is maintained. New client applications can serialize the new component properly. However, the serialization information captured by the old applications is now incompatible, and will result in a SerializationException if used in .NET 1.1. The vendor can, of course, increment the assembly version number, but doing so will prevent the old clients from taking advantage of the new functionality. The formatters in .NET 2.0 were redesigned to handle such predicaments.

In the case of removing an unused member variable, the binary formatter will simply ignore the additional information found in the stream. For example, suppose you use this class (but not a struct) definition and serialize it using a binary formatter:

    //Version 1.0
    [Serializable]
    public class MyClass
    {
       public int Number1;
       public int Number2;
    }

Without changing the assembly version, remove one of the member variables:

    //Version 2.0
    [Serializable]
    public class MyClass
    {
       public int Number1;
    }

You can then rebuild, redeploy, and deserialize instances of version 2.0 of MyClass with the serialization information captured using version 1.0 of MyClass.

The real challenge in type-version tolerance is dealing with new members, because the old serialization information does not contain any information about them. By default, the formatters are not tolerant toward the new members and will throw an exception when they encounter them.

.NET 2.0 addresses this problem by providing a field attribute called OptionalField—a simple attribute with a single public property of type int, called VersionAdded:

    [AttributeUsage(AttributeTargets.Field,Inherited = false)]
    public sealed class OptionalFieldAttribute : Attribute
    {
       public int VersionAdded(get;set);
    }

Applying the OptionalField attribute has no effect during serialization, and fields marked with it will be serialized into the stream. This is because OptionalField is meant to be applied on new fields of your type, and it causes the formatters to ignore the new members during deserialization:

    //Version 1.0
    [Serializable]
    public class MyClass
    {
       public int Number1;
    }
    //Version 2.0
    [Serializable]
    public class MyClass
    {
       public int Number1;
    
       [OptionalField]
       public int Number2;
    }

That said, if the new member variable has a good-enough default value, such as the application's default directory or user preferences, you can use values provided by the new clients to synthesize values for the old clients. You will need to provide these values in your handling of the deserializing event. If you do so before deserialization and the stream does contain serialized values, the serialized values are preferable to the synthesized ones, and the deserialization process will override the values you set in the handling of the deserializing event.

Consider, for example, this class version:

    //Version 1.0
    [Serializable]
    public class MyClass
    {
       public int Number1;
    }

Suppose you want to add a new class member called Number2, while using the old serialization information. You need to provide handling to the deserializing event, and in it initialize Number2:

    [Serializable]
    public class MyClass
    {

       public int Number1;
       [OptionalField]
       public int Number2;

       [OnDeserializing]
       void OnDeserializing(StreamingContext context)
       {
          Number2 = 123;
       }
    }

But what if the values you synthesize are somehow dependent on the version of the class in which they are added? You can store version information in the OptionalField attribute, using its VersionAdded member:

    [OptionalField(VersionAdded = 1)]
    public int Number2;

In the deserializing event handler you will need to use reflection to read the value of the VersionAdded field and act accordingly, as shown in Example 3. This example uses the helper method OptionalFieldVersion( ) of the SerializationUtil static helper class. OptionalFieldVersion( ) accepts the type and the member variable name to reflect, returning the value of the VersionAdded field:

    public static string OptionalFieldVersion(Type type,string member);

Example 3. Relying on VersionAdded
[Serializable]
public class MyClass
{
   public int Number1;

   [OptionalField(VersionAdded = 1)]
   public int Number2;


   [OnDeserializing]
   void OnDeserializing(StreamingContext context)
   {
      int versionAdded;
      versionAdded = SerializationUtil.OptionalFieldVersion(typeof(MyClass),
                                                            "Number2");
      if(versionAdded == 1)
         Number2 = 123;
      if(versionAdded == 2)
         Number2 = 456;
   }
}

public static class SerializationUtil
{
   public static int OptionalFieldVersion(Type type,string member)
   {
      Debug.Assert(type.IsSerializable);

      MemberInfo[] members = type.GetMember(member,BindingFlags.Instance |
                                                   BindingFlags.NonPublic|
                                                   BindingFlags.Public   |
                                                   BindingFlags.DeclaredOnly);
      Debug.Assert(members.Length == 1);
      object[] attributes =
    members[0].GetCustomAttributes(typeof(OptionalFieldAttribute),false);
      Debug.Assert(attributes.Length == 1);//Exactly one attribute is expected

      OptionalFieldAttribute attribute;
      attribute = attributes[0] as OptionalFieldAttribute;
      return attribute.VersionAdded;
   }
}

					  

The next version of the serialization infrastructure (a part of Indigo) will provide a formatter that is version-tolerant, as well as support for version-added information for the optional fields.

Other  
  •  All About Trackpads & Touchscreens
  •  Use Unified Communications To Optimize Business Procedures
  •  How To Buy A Tablet
  •  Best Brands In Electronic World (Part 2)
  •  Best Brands In Electronic World (Part 1)
  •  Top 10 Electronics Devices – May 2013
  •   Protect Your Business
  •  Downloading and Installing Nginx HTTP Server : Adding Nginx as a system service
  •  Downloading and Installing Nginx HTTP Server : Controlling the Nginx service
  •  Downloading and Installing Nginx HTTP Server : Configure options
  •  Windows System Programming : Exception Handling - Using Termination Handlers to Improve Program Quality, Using a Filter Function
  •  Windows System Programming : Exception Handling - Termination Handlers
  •  Performing mySAP.com Component Installations : Installation Details for mySAP Components
  •  Performing mySAP.com Component Installations : Installing Your mySAP-Enabling Foundation—NetWeaver
  •  Windows System Programming : Exception Handling - Treating Errors as Exceptions
  •  Windows System Programming : Exception Handling - Floating-Point Exceptions, Errors and Exceptions
  •  New Gadgets For April 2013 (Part 5)
  •  New Gadgets For April 2013 (Part 3)
  •  New Gadgets And Accessories – April 2013
  •  Vodafone Sure Signal - A Tiny Mast That Packs A Lot Of Power
  •  
    Most View
    20 Essential Window 8 Apps (Part 3)
    The Best Apps and Gear of 2012 (Part 6)
    Lab Test: Satellite - SKY 2TB (Part 1)
    New Restrictions On Old Office Software (Part 1)
    20 Essential Window 8 Apps (Part 1)
    Create And Share Your First Photo Journal
    AMD FX – Can They Challenge Intel’s Mid-Range?
    Sony Vaio SVT13116FGS Review - A Third Gen Core i7 Ultrabook With Plenty Of Style
    Focal XS Book Music System
    Panasonic G X Vario PZ 14-42mm f/3.5 - 5.6 ASPH Power OIS
    Top 10
    Blackberry Z10 - It’s Up Against Some Tough Competition
    Galaxy S4 - Make Your Life Richer, Simpler, And More Fun (Part 3)
    Galaxy S4 - Make Your Life Richer, Simpler, And More Fun (Part 2)
    Galaxy S4 - Make Your Life Richer, Simpler, And More Fun (Part 1)
    Google Nexus 7 - Thin, Light, And Designed For Google Play
    Group Test - Mid-Range Maestros (Part 4) : Motorola RAZR i, Motorola RAZR I specs
    Group Test - Mid-Range Maestros (Part 3) : HTC One SV
    Group Test - Mid-Range Maestros (Part 2) : Samsung Galaxy S3 Mini
    Group Test - Mid-Range Maestros (Part 1) : Sony Xperia T
    Archos GamePad - A Video Gaming Tablet