« WP7 turn-by-turn directions have accents | Main | Serializing to Binary on WP7 - Part 1 »
Thursday
May262011

Serializing to Binary on WP7 - Part 2

After investigating a couple of 3rd party Binary Serialization libraries for WP7 and having seen a couple of suggestions online of just writing your own, I decided to do just that.

There were 2 main places where I found information on how to potentially write you own Binary Serializer for WP7, the first I felt took a rather old school approach by adding a "Write" function to each class which accepted a BinaryWriter and simply wrote out each of its own properties. The clear problem with this approach is the maintenance involved with adding new properties or if you wanted to serialize a different class.

The second approach was explained by Eugene Chaikin on his site, where he attempted to replicate the DataContractSerializer's interface and even make use of the '[DataMember]' attributes in the same way.

So  based on the simple sample code I started to create my own and call it the VSBinarySerialiser (because this was for use on VerySoftware [VS] projects, and Serialise is spelt with an S in the UK). The goal was to be able to use code like the following directly in place of the equivalent DataContractSerializer code:

   1:  // Write
   2:  var serialiser = new VSBinarySerialiser(typeof(MyClass));
   3:  serialiser.WriteObject(fs, theData);
   4:   
   5:  // Read
   6:  var serialiser = new VSBinarySerialiser(typeof(MyClass));
   7:  var result = (MyClass)serialiser.ReadObject(fs);

The first obvious change I needed to make was due to the fact that my classes contained other classes inside of them, so I needed a ReadObject/WriteObject function that I could use internally to recursively serialise objects that were encapsulated within each other. To do this I modified the public ReadObject/WriteObject functions to simply create the BinaryReader/BinaryWriter and then call into the internal function passing the type that was stored when the VSBinarySerialiser was created:

 

   1:  public void WriteObject(Stream stream, object theData)
   2:  {
   3:      if (stream == null || theData == null)
   4:          return;
   5:   
   6:      BinaryWriter bw = new BinaryWriter(stream);
   7:   
   8:      InternalWriteObject(bw, theData, serializableObjectType);
   9:  }
  10:   
  11:  public object ReadObject(Stream stream)
  12:  {
  13:      if (stream == null)
  14:          return null;
  15:   
  16:      BinaryReader br = new BinaryReader(stream);
  17:   
  18:      return InternalReadObject(br, serializableObjectType);
  19:  }

 

The Internal Read/Write Object functions I wrote were similar to those in Eugene's example, except for 3 main changes. Firstly I gathered the list of Properties on the object marked with the attribute at this point rather than earlier, as this allows recursive use of the function. Secondly to support the writing out of null objects when calling this function recursively I write a boolean out at the start to indicate if the object is Null or not. Finally I extended the list of type checks to include the additional data types I needed:

 

   1:  private void InternalWriteObject(BinaryWriter bw, object obj, Type type)
   2:  {
   3:      if (obj == null)
   4:      {
   5:          bw.Write(false);
   6:          return;
   7:      }
   8:      else
   9:      {
  10:          bw.Write(true);
  11:      }
  12:   
  13:      var serializableProperties = GetMarkedProperties(type);
  14:   
  15:      foreach (PropertyInfo pi in serializableProperties)
  16:      {
  17:          var value = pi.GetValue(obj, null);
  18:   
  19:          if (pi.PropertyType == typeof(string))
  20:          {
  21:              bw.Write(value as string ?? string.Empty);
  22:          }
  23:          else if (pi.PropertyType == typeof(double))
  24:          {
  25:              bw.Write((double)value);
  26:          }
  27:          else if (pi.PropertyType == typeof(bool))
  28:          {
  29:              bw.Write((bool)value);
  30:          }
  31:          else if (pi.PropertyType == typeof(DateTime))
  32:          {
  33:              bw.Write(((DateTime)value).ToUniversalTime().Ticks);
  34:          }
  35:          else if (pi.PropertyType == typeof(myEnum))
  36:          {
  37:              bw.Write((int)value);
  38:          }
  39:          else if (pi.PropertyType == typeof(MyOtherClass))
  40:          {
  41:              InternalWriteObject(bw, value as Language, typeof(MyOtherClass));
  42:          }
  43:          else if (pi.PropertyType == typeof(ObservableCollection<MyThirdClass>))
  44:          {
  45:              WriteObsevableCollection<MyThirdClass>(bw, value as ObservableCollection<MyThirdClass>);
  46:          }
  47:          else
  48:          {
  49:              throw new NotImplementedException(String.Format("Attempted to WriteObject property of type '{0}' which is currently unsupported!", pi.PropertyType));
  50:          }
  51:      }
  52:  }
  53:   
  54:   
  55:  private object InternalReadObject(BinaryReader br, Type type)
  56:  {
  57:      if (!br.ReadBoolean())
  58:      {
  59:          return null;
  60:      }
  61:   
  62:      object deserializedObject = Activator.CreateInstance(type);
  63:   
  64:      var serializableProperties = GetMarkedProperties(type);
  65:   
  66:      foreach (PropertyInfo pi in serializableProperties)
  67:      {
  68:          Type propType = pi.PropertyType;
  69:   
  70:          if (pi.PropertyType == typeof(string))
  71:          {
  72:              pi.SetValue(deserializedObject, br.ReadString(), null);
  73:          }
  74:          else if (pi.PropertyType == typeof(double))
  75:          {
  76:              pi.SetValue(deserializedObject, br.ReadDouble(), null);
  77:          }
  78:          else if (pi.PropertyType == typeof(bool))
  79:          {
  80:              pi.SetValue(deserializedObject, br.ReadBoolean(), null);
  81:          }
  82:          else if (pi.PropertyType == typeof(DateTime))
  83:          {
  84:              pi.SetValue(deserializedObject, new DateTime(br.ReadInt64(), DateTimeKind.Utc), null);
  85:          }
  86:          else if (pi.PropertyType == typeof(myEnum))
  87:          {
  88:              pi.SetValue(deserializedObject, (AppStore)br.ReadInt32(), null);
  89:          }
  90:          else if (pi.PropertyType == typeof(MyOtherClass))
  91:          {
  92:              pi.SetValue(deserializedObject, InternalReadObject(br, typeof(MyOtherClass)) as MyOtherClass, null);
  93:          }
  94:          else if (pi.PropertyType == typeof(ObservableCollection<MyThirdClass>))
  95:          {
  96:              pi.SetValue(deserializedObject, ReadObservableCollection<MyThirdClass>(br), null);
  97:          }
  98:          else
  99:          {
 100:              throw new NotImplementedException(String.Format("Attempted to ReadObject property of type '{0}' which is currently unsupported!", pi.PropertyType));
 101:          }
 102:      }
 103:   
 104:      return deserializedObject;
 105:  }

 I've changed the names of some of the classes here to protect the innocent :)

 

The other change you might notice in the above code is the support for ObservableCollections, as we use a number of these for storing lists of data I created generic functions called WriteObsevableCollection<T> and ReadObservableCollection<T> to handle these:

 

   1:  private void WriteObsevableCollection<T>(BinaryWriter bw, ObservableCollection<T> collection)
   2:  {
   3:      if (collection == null || collection.Count == 0)
   4:      {
   5:          bw.Write(0);
   6:      }
   7:      else
   8:      {
   9:          bw.Write(collection.Count);
  10:          foreach (var item in collection)
  11:          {
  12:              InternalWriteObject(bw, item, typeof(T));
  13:          }
  14:      }
  15:  }
  16:   
  17:  private ObservableCollection<T> ReadObservableCollection<T>(BinaryReader br)
  18:  {
  19:      var result = new ObservableCollection<T>();
  20:      int count = br.ReadInt32();
  21:      for (int i = count; i > 0; --i)
  22:      {
  23:          result.Add((T)InternalReadObject(br, typeof(T)));
  24:      }
  25:   
  26:      return result;
  27:  }

 

And that's it, we now have our own Binary Serialiser that works on WP7; but how does it compare performance wise? Below is a table showing the data for our VSBinarySerialiser against the DataContractSerializer (averages are based on 5 runs of each):

 Avg. Save time (seconds)Avg. Load time (seconds)
DataContractSerializer 1.20 4.37
VSBinarySerialiser 1.25 2.63

 

Not too bad, the time to write/serialize the data is slightly slower than the DataContractSerializer but the read/deserialize takes just 60% of the original time. I expect the slower write time is possibly down to something relating to how I'm reflecting over the classes to get the properties each time, rather than caching them for each Type I come across. That optimisation however can wait for another time.

Reader Comments (1)

Did you ever tackle nullable types?

July 12, 2011 | Unregistered CommenterRobb Schiefer

PostPost a New Comment

Enter your information below to add a new comment.

My response is on my own website »
Author Email (optional):
Author URL (optional):
Post:
 
Some HTML allowed: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <code> <em> <i> <strike> <strong>