Beginner’s Guide to .NET Serialization

One of the terms that confused me when I first started .NET was the term ‘serialization’. During my time at university and my subsequent career in programming, I’d never come across any problem that had directed me towards finding out about serialization. After seeing some project documentation that made heavy use of the Serializable attribute, I decided to investigate, and quickly found out some answers.

Back in university, if I wanted to save an object to a file, I’d design the file using some Jackson Structure Programming and write a Save() method that would do it for me. As it turns out, I was doing a lot of redundant work.

Serialization is the process of saving the state of an object. Exactly the same thing I was doing with my Save() methods. Using built in .NET serialization, you can save the state of any object to any stream (although there are some pitfalls with generic types). The best thing is that in a lot of cases, the only thing you need to do to make any of your classes serializable is to add the [Serializable] attribute to your class.

Take for instance the class Box:

  1. [Serializable]
  2. public class Box
  3. {
  4.     private double height;
  5.     private double width;
  6.     private double depth;
  7.     private double volume;
  8.  
  9.     /*
  10.     Public properties, read only for Volume.
  11.     */
  12.  
  13.     public Box(double h, double w, double d)
  14.     {
  15.         height = h;
  16.         width = w;
  17.         depth = d;
  18.         CalculateVolume();
  19.     }
  20.  
  21.     private void CalculateVolume()
  22.     {
  23.         volume = height * width * depth;
  24.     }
  25. }

Box represents a rectangular box (or right-angled parallelepiped), with a height, width, depth and volume. Don’t criticize the volume field just yet, it’s there to demonstrate a feature of serialization.

With the attribute Serializable, all we need to do to save the state of the box is to create an instance of a formatter object. The main types of serialization are Binary, Soap, and Xml, their corresponding types being System.Runtime.Serialization.Formatters.Binary.BinaryFormatter, System.Runtime.Serialization.Formatters..Soap.SoapFormatter and System.Xml.Serialization.XmlSerializer. For now, I’m going to use BinaryFormatter. SoapFormatter works in a similar way to BinaryFormatter. XmlSerializer can be a little more complicated, and has some limitations which I’m not going to go into.

So, to serialize a Box object, we can do the following:

  1. Box myBox = new Box(12d, 15d, 18d);
  2. BinaryFormatter bf = new BinaryFormatter();
  3. Stream s = File.Open(@”C:\myBox.box", FileMode.Create);
  4. bf.Serialize(s, myBox);
  5. s.Close();

There, the box has been saved to a file. To later access the object, we can deserialize it doing the following:

  1. BinaryFormatter bf = new BinaryFormatter();
  2. Stream s = File.Open(@”C:\myBox.box", FileMode.Open);
  3. Box myBox = (Box)bf.Deserialize(s);
  4. s.Close();

There, done. Or maybe not. Although the process above will work, it’s not as efficient as it could be. We’re serializing Box.volume, which is derived from the other three fields in Box. We could save some disk space if we don’t serialize that field. Luckily, this is pretty easy as well. We’re simply going to add the NonSerialized attribute to volume:

  1. [NonSerialized]private double volume;

Now when we serialize, volume won’t be saved, and the Box instance will take up less space. The problem now comes with deserialization. On deserialization, no constructors are called, the appropriate fields are simply initialized. With these changes, deserializing a Box will result in it having a volume of 0, the default value for a double.

To cater for this, we can implement the IDeserializationCallback interface, and implement OnDeserialization. We’ve already shown some forward thinking by implementing the CalculateVolume() method, all that’s needed is a call to that method when deserialization is complete.

So, the complete Box class (omitting properties) looks like this:

  1. public class Box : IDeserializationCallback
  2. {
  3.     private double height;
  4.     private double width;
  5.     private double depth;
  6.     [NonSerialized]private double volume;
  7.  
  8.     /*
  9.     Public properties, read only for Volume.
  10.     */
  11.  
  12.     public Box(double h, double w, double d)
  13.     {
  14.         height = h;
  15.         width = w;
  16.         depth = d;
  17.         CalculateVolume();
  18.     }
  19.  
  20.     private void CalculateVolume()
  21.     {
  22.         volume = height * width * depth;
  23.     }
  24.  
  25.     public void OnDeserialization(object sender)
  26.     {
  27.         CalculateVolume();
  28.     }
  29. }

This works fine, we’ve saved space on our disk. But what happens if we want to change the class and add new members. Will deserialization work in this case? The answer is that you can add new members, but you may need to write a bit of code to ensure that older serialized objects are compatible with the new version of the class.

The easiest way to do this is to add the OptionalField attribute to any new members. If it’s important that these members are initilized to meaningful values, then initialize them in OnDeserialization. For example:

  1. public class Box : IDeserializationCallback
  2. {
  3.     private double height;
  4.     private double width;
  5.     private double depth;
  6.     [NonSerialized]private double volume;
  7.     [OptionalField]private double maxWeightKilos;
  8.  
  9.     /*
  10.     Public properties, read only for Volume.
  11.     */
  12.  
  13.     public Box(double h, double w, double d, double mwk)
  14.     {
  15.         height = h;
  16.         width = w;
  17.         depth = d;
  18.         maxWeightKilos = mwk;
  19.         CalculateVolume();
  20.     }
  21.  
  22.     private void CalculateVolume()
  23.     {
  24.         volume = height * width * depth;
  25.     }
  26.  
  27.     public void OnDeserialization(object sender)
  28.     {
  29.         if(maxWeightKilos == 0)
  30.         {
  31.             maxWeightKilos = 1.2d;
  32.         }
  33.         CalculateVolume();
  34.     }
  35. }

In the example above, any serialized instances of the new class will have maxWeightKilos saved. Any old serialized instances that are being deserialized will have the value initialized to 1.2.

There, I think that’s enough serialization for now, it’s quite a large subject, and this example just shows some of the basics. If you’d like to know more, I’d suggest looking at the ISerializable interface and custom serialization, or the other formatter classes, SoapFormatter and XmlSerializer.

Share and Enjoy:
  • Print
  • Digg
  • StumbleUpon
  • del.icio.us
  • Facebook
  • Yahoo! Buzz
  • Twitter
  • Google Bookmarks
  • email
  • LinkedIn
  • Technorati

Leave a Reply

Your email address will not be published. Required fields are marked *