Section 9.6. Serialization and Class Hierarchies


9.6. Serialization and Class Hierarchies

When you apply the Serializable attribute to a class, it affects only that classit doesn't make any derived classes serializable, because the Inherited property of the AttributeUsage attribute applied on the Serializable attribute is set to false. For example, if you derive MyClass from MyBaseClass, MyClass isn't serializable:

     [Serializable]     public class MyBaseClass     {}     public class MyClass : MyBaseClass     {}

At first glance this may appear awkward, but it does make design sense: MyBaseClass has no way of knowing whether its subclasses will have non-serializable members, so it would be wrong for them to automatically inherit the serializable status. If you design a class hierarchy and you want to support serialization of any type in the hierarchy, be sure to mark each level with the Serializable attribute:

     [Serializable]     public class MyBaseClass     {}     [Serializable]     public class MyClass : MyBaseClass     {}     [Serializable]     public class MyOtherClass : MyClass     {}

9.6.1. Custom Serialization and Base Classes

If any of the classes in the hierarchy implements ISerializable, there are a few design guidelines you have to follow to allow subclasses to provide their own custom serialization and to correctly manage the custom serialization of the base classes:

  • Only the topmost base class that uses custom serialization needs to derive from ISerializable.

  • When using implicit interface implementation, a base class must define its GetObjectData( ) method as virtual to allow subclasses to override it. When using explicit interface implementation, follow the technique shown in Chapter 3 for combining explicit interface implementation and a class hierarchy.

  • In a subclass implementation of GetObjectData( ), after serializing its own state, the subclass must call its base class's implementation of GetObjectData( ).

  • In its implementation of the deserializing constructor, the subclass must call its base class's deserializing constructor.

  • The deserializing constructor should be protected, to allow a subclass to call its base class's deserializing constructor.

Example 9-20 demonstrates how to implement these points; you can use it as a template for combining custom serialization and class hierarchies.

Example 9-20. Combining custom serialization with a class hierarchy
 [Serializable] public class MyBaseClass : ISerializable {    public MyBaseClass(  )    {}    int m_BaseNumber;    public virtual void GetObjectData(SerializationInfo info,                                      StreamingContext context)    {       //Add MyBaseClass members       GenericSerializationInfo genericInfo = new GenericSerializationInfo(info);       genericInfo.AddValue("m_BaseNumber",m_BaseNumber);    }    protected MyBaseClass(SerializationInfo info,StreamingContext context)    {       //Read MyBaseClass members and initialize them       GenericSerializationInfo genericInfo = new GenericSerializationInfo(info);       m_BaseNumber = genericInfo.GetValue<int>("m_BaseNumber");    } } [Serializable] public class MySubClass : MyBaseClass {    public MySubClass(  )    {}    int m_SubNumber;    public override void GetObjectData(SerializationInfo info,                                       StreamingContext context)    {       //Add MySubClass members       GenericSerializationInfo genericInfo = new GenericSerializationInfo(info);       genericInfo.AddValue("m_SubNumber",m_SubNumber);       base. GetObjectData(info,context);    }    protected MySubClass(SerializationInfo info,StreamingContext context):                                                                  base(info,context)    {       //Read MySubClass members and initialize them       GenericSerializationInfo genericInfo = new GenericSerializationInfo(info);       m_SubNumber = genericInfo.GetValue<int>("m_SubNumber");    } }

If a base class provides custom serialization, all subclasses derived from it can use only custom serialization.


9.6.2. Manual Base-Class Serialization

Combining class hierarchies and serialization, whether fully automatic or custom, is straightforward: all classes either use only the Serializable attribute, or use the attribute and also implement ISerializable. However, the picture isn't so clear when it comes to deriving a serializable class from a class not marked with the Serializable attribute, as in this case:

     public class MyBaseClass     {}     [Serializable]     public class MySubClass : MyBaseClass     {}

In fact, in such a case, .NET can't serialize objects of type MySubClass at all, because it can't serialize their base classes. Trying to serialize an object of type MySubClass results in an exception of type SerializationException. Such a situation may occur when deriving from a class in a third-party assembly where the vendor neglected to mark its class as serializable.

The good news is that there is a workaround for such a case. The solution presented here isn't a sure cure, because it assumes that none of the base classes require custom serialization steps. It merely compensates for the oversight of not marking the base class as serializable.

The workaround is simple: the subclass can implement ISerializable, use reflection to read and serialize the base classes' fields, and use reflection again to set these fields during deserialization. The static SerializationUtil helper class provides the two static methods SerializeBaseType( ) and DeserializeBaseType( ), defined as:

     public static class SerializationUtil     {        public static void SerializeBaseType(object obj,                                             SerializationInfo info,                                             StreamingContext context);        public static void DeserializeBaseType(object obj,                                               SerializationInfo info,                                               StreamingContext context);        //Rest of SerializationUtil     }

All the subclass needs to do is implement ISerializable and use SerializationUtil to serialize and deserialize its base classes:

     public class MyBaseClass     {}     [Serializable]     public class MySubClass : MyBaseClass,ISerializable     {        public MySubClass(  )        {}        public void GetObjectData(SerializationInfo info,StreamingContext context)        {           SerializationUtil.SerializeBaseType(this,info,context);        }        protected MySubClass(SerializationInfo info,StreamingContext context)        {           SerializationUtil.DeserializeBaseType(this,info,context);        }     }

If the subclass itself has no need for custom serialization and only implements ISerializable to serialize its base class, you can use SerializationUtil to serialize the subclass as well. SerializationUtil provides these overloaded versions of SerializeBaseType( ) and DeserializeBaseType( ):

     public static void SerializeBaseType(object obj,bool serializeSelf,                                          SerializationInfo info,                                          StreamingContext context);     public static void DeserializeBaseType(object obj,bool deserializeSelf,                                            SerializationInfo info,                                            StreamingContext context);

These versions accept a flag instructing them whether to start serialization with the type itself instead of its base class:

     public void GetObjectData(SerializationInfo info,StreamingContext context)     {        //Serializing this type and its base classes        SerializationUtil.SerializeBaseType(this,true,info,context);     }     protected MyClass(SerializationInfo info,StreamingContext context)     {        //Deserializing this type and its base classes        SerializationUtil.DeserializeBaseType(this,true,info,context);     }

SerializationUtil is also useful in cases where a class needs to provide custom serialization, even though simple use of the Serializable attribute would have sufficed for that class. This may occur because, as mentioned previously, if any class in a class hierarchy provides custom serialization, all its subclasses must do so as well. It can also occur when a type is constrained to implement Iserializable, to be used as a generic type parameter in a generic class.


Example 9-21 demonstrates the implementations of SerializeBaseType and DeserializeBaseType.

Example 9-21. Implementing SerializeBaseType and DeserializeBaseType
 public static class SerializationUtil {    public static void SerializeBaseType(object obj,                                    SerializationInfo info,StreamingContext context)    {       Type baseType = obj.GetType(  ).BaseType;       SerializeBaseType(obj,baseType,info,context);    }    static void SerializeBaseType(object obj,Type type,SerializationInfo info,                                                           StreamingContext context)    {       if(type == typeof(object))       {          return;       }       BindingFlags flags = BindingFlags.Instance|BindingFlags.DeclaredOnly|                            BindingFlags.NonPublic|BindingFlags.Public;       FieldInfo[] fields = type.GetFields(flags);       foreach(FieldInfo field in fields)       {          if(field.IsNotSerialized)          {             continue;          }          string fieldName = type.Name + "+" + field.Name;          info.AddValue(fieldName,field.GetValue(obj));       }       SerializeBaseType(obj,type.BaseType,info,context);    }    public static void DeserializeBaseType(object obj,SerializationInfo info,                                                           StreamingContext context)    {       Type baseType = obj.GetType(  ).BaseType;       DeserializeBaseType(obj,baseType,info,context);    }    static void DeserializeBaseType(object obj,Type type,SerializationInfo info,                                                           StreamingContext context)    {       if(type == typeof(object))       {          return;       }       BindingFlags flags = BindingFlags.Instance|BindingFlags.DeclaredOnly|                            BindingFlags.NonPublic|BindingFlags.Public;       FieldInfo[] fields = type.GetFields(flags);       foreach(FieldInfo field in fields)       {          if(field.IsNotSerialized)          {             continue;          }          string fieldName = type.Name + "+" + field.Name;          object fieldValue = info.GetValue(fieldName,field.FieldType);          field.SetValue(obj,fieldValue);       }       DeserializeBaseType(obj,type.BaseType,info,context);    }    //Rest of SerializationUtil }

When SerializationUtil serializes an object's base class, it needs to serialize all the base classes leading to that base class as well. You can access the base-class type using the BaseType property of Type:

     Type baseType = obj.GetType(  ).BaseType;

With the GetFields( ) method of Type, you can get all the fields (private and public) declared by the type, as well as any public or protected fields available via its own base classes. This isn't good enough for serialization, though, because you need to capture all the private fields available from all levels of the class hierarchy, including ones with the same name. The solution is to serialize each level of the class hierarchy separately and thus access each level's private fields. SerializeBaseType( ) calls a private helper method, also called SerializeBaseType( ), providing it with the level of the class hierarchy to serialize:

     SerializeBaseType(obj,baseType,info,context);

The private SerializeBaseType( ) serializes that level and then calls itself recursively, serializing the next level up the hierarchy:

     SerializeBaseType(obj,type.BaseType,info,context);

The recursion stops once it reaches the System.Object level:

     static void SerializeBaseType(object obj,Type type,SerializationInfo info,                                   StreamingContext context)     {        if(type == typeof(object))        {           return;        }        /* Rest of the implementation */     }

To serialize a particular level, the private SerializeBaseType( ) calls GetFields( ) with a binding flags mask (BindingFlags.DeclaredOnly), which instructs it to return all fields defined by this type only and not its base types. This ensures that as it visits the next levels up the hierarchy, it doesn't end up serializing fields more than once. It also binds to instance and not static fields, because static fields are never serialized:

     BindingFlags flags = BindingFlags.Instance|BindingFlags.DeclaredOnly|                          BindingFlags.NonPublic|BindingFlags.Public;

The private SerializeBaseType( ) then calls GetFields( ) and stores the result in an array of FieldInfo objects:

     FieldInfo[] fields = type.GetFields(flags);

This solution needs to deal with a class hierarchy in which some levels actually use the Serializable attribute, such as class A in this example:

     [Serializable]     class A     {}     class B : A     {}     [Serializable]     class C : B,ISerializable     {...}

Because class A may contain some fields marked with the NonSerialized attribute, the solution needs to check that the fields are serializable. This is easy to do via the IsNotSerialized Boolean property of FieldInfo:

     foreach(FieldInfo field in fields)     {        if(field.IsNotSerialized)        {           continue;        }        //Rest of the iteration loop     }

Since different levels can declare private fields with the same names in the same class hierarchy, the private SerializeBaseType( ) prefixes each field name with its declaring type separated by a +:

     string fieldName = type.Name + "+" + field.Name;

The value of a field is obtained via the GetValue( ) method of FieldInfo and is then added to the info parameter:

     info.AddValue(fieldName,field.GetValue(obj));

Deserialization of the base class (or classes) is similar to serialization and is also done recursively until the System.Object level is reached. The public DeserializeBaseType( ) method accesses the base type and calls the private helper method DeserializeBaseType( ). At each level in the class hierarchy, the private DeserializeBaseType( ) retrieves the collection of fields for that type. For each field, it creates a name by appending the name of the current level to the name of the field, gets the value from info, and sets the value of the corresponding field, using the SetValue( ) method of the FieldInfo class:

     string fieldName = type.Name + "+" + field.Name;     object fieldValue =     info.GetValue(fieldName,field.FieldType);field.SetValue(obj,fieldValue);



Programming. NET Components
Programming .NET Components, 2nd Edition
ISBN: 0596102070
EAN: 2147483647
Year: 2003
Pages: 145
Authors: Juval Lowy

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net