11.9 Serializable and ISerializable

11.9 [Serializable] and ISerializable

Eagle-eyed readers will have noticed that in all the preceding examples the classes are sealed . This is no accident . Marking a class as [Serializable] and deciding whether to implement ISerializable has some specific implications for both base and derived types, and deserves separate discussion.

Consider the following serializable class hierarchy:

 [Serializable] public class Person {   public string Name;   public int Age;   // Rest of class... }    [Serializable] public sealed class Student: Person {   public string Course;   // Rest of class... } 

In this example both Person and Student are serializable, and both classes use the default runtime serialization behavior since neither class implements ISerializable .

Now imagine that the developer of Person decides for some reason to implement ISerializable and provide a deserialization constructor to control Person serialization. The new version of Person might look like this:

 [Serializable] public class Person : ISerialization {   public string Name;   public int Age;   public void GetObjectData(SerializationInfo si, StreamingContext sc) {     si.AddValue("IChangedTheNameFieldName", Name);     si.AddValue("IChangedTheAgeFieldName", Age);   }   protected Person(SerializationInfo si, StreamingContext sc)  {     Name = si.GetString("IChangedTheNameFieldName");     Age = sc.GetInt32("IChangedTheAgeFieldName");   }   // Rest of class... } 

Although this works for instances of Perso n, this change breaks serialization of Student instances. Serializing a Student instance would appear to succeed, but the Course field in the Student type isn't saved to the stream because the implementation of ISerializable.GetObjectData on Person has no knowledge of the members of the Student derived type. Additionally, deserialization of Student instances throws an exception since the runtime is looking (unsuccessfully) for a deserialization constructor on Student .

Fundamentally, if a base class implements ISerializable and the other serialization- related interfaces and base classes, derived classes need to follow suit. There are a several ways of accomplishing this. One option is for the base class to declare GetObjectData as virtual , allowing derived classes to simply override it as needed and call up the base type's GetObjectData as needed. Another option is for the derived class to reimplement ISerializable using explicit interface implementation, calling up to the base implementation as needed. In all cases, derived types need to provide deserialization constructors, and chain up to the base deserialization constructor as needed. An implementation of Student that takes the latter approach looks like this:

 [Serializable] public sealed class Student : Person, ISerialization {   public string Course;   void ISerialization.GetObjectData(SerializationInfo si,                                      StreamingContext sc) {     si.AddValue("IChangedTheCourseFieldName", Course);     base.GetObjectData(si, sc);   }   private Student(SerializationInfo si, StreamingContext sc)     : base(si, sc) {     Course = si.GetString("IChangedTheCourseFieldName");   }   // Rest of class... } 

Now imagine a case in which the developer of Person elects not to implement ISerializable , relying instead on the default runtime serialization behavior. A derived type such as Student wishing to control its serialization by implementing ISerializable needs to ensure that the Person members are correctly serialized as part of the GetObjectData call, and correctly initialized by the Student deserialization constructor. While this could be performed manually in the derived type for public and protected members of the base type, the problem remains for private members of the base type that need serialization and deserialization.

The problem is that Person does not have an ISerializable.GetObjectData method or a deserialization constructor that the Student type can delegate to. The solution to this problem is the FormatterServices class, which provides a standard way to retrieve the list of all members in a type that need to be serialized ( GetSerializableMembers ), as well as a standard way to retrieve a serialized representation of these members ( GetObjectData ), and a standard way to deserialize this representation back into a new instance ( PopulateObjectMembers ). An implementation of Student that uses FormatterServices looks like this:

 [Serializable] public sealed class Student : Person, ISerializable {   public string Course;   public void GetObjectData(SerializationInfo si, StreamingContext sc) {     Type t = typeof(Person);     MemberInfo[ ] mbrs = FormatterServices.GetSerializableMembers(t);     object[ ] data = FormatterServices.GetObjectData(this, mbrs);     si.AddValue("BaseData", data);     si.AddValue("IChangedTheCourseFieldName", Course);   }   private Student(SerializationInfo si, StreamingContext sc) {     Type t = typeof(Person);     MemberInfo[ ] mbrs = FormatterServices.GetSerializableMembers( );     object[ ] data = (object[ ])si.GetValue("BaseData", typeof(object[ ]));     FormatterServices.PopulateObjectMembers(this, mbrs, data);     Course = si.GetString("IChangedTheCourseFieldName");   }   // Rest of class... } 


C# in a Nutshell
C # in a Nutshell, Second Edition
ISBN: 0596005261
EAN: 2147483647
Year: 2005
Pages: 963

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