Serialization of objects as XML or Binary Data
In .net applications there is a very useful feature which allows us to serialise an object as either xml or binary data.
What that means is that we can take an instance of an object running in our program and with just a few lines of code we can persist this to disk for example so that later on, perhaps even after we have closed down the program and opened it up another day, we are able to read that data back from disk and recreate our object just as it was when we serialised it.
This is incredibly useful particularly since we do not have to persist to disk. We can serialise our object to a stream which gives us endless possibiliies. For example we can serialise an object and transfer it across a network and recreate it at the other end - just like the transporters on Star Trek!
As well as being able to serialise to streams, we can also serialise to TextWriters and StreamWriters. Consider the following entire console application when we are able to create an instance of a Person object and then serialise it to the Console in really just 2 lines of code!
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Serialization;
namespace ConsoleSerialisation
{
public class Program
{
static void Main(string[] args)
{
Person person = new Person();
person.FirstName = "Sarah";
person.SurName = "Smith";
person.Age = 28;
XmlSerializer serialiser = new XmlSerializer(person.GetType());
serialiser.Serialize(Console.Out, person);
Console.Read();
}
}
[Serializable]
public class Person
{
public string FirstName
{
get;
set;
}
public string SurName
{
get;
set;
}
public int Age
{
get;
set;
}
}
}
The output should look like this:
Deserialisation of an object is pretty much the reverse process but we cannot do that yet until we have something to read our data from. We can't do it from the console right!
There are diferent serialisation classes and different methods for using those for serialising in different ways and to different objects - eg Streams, StreamWriters etc. We won't cover all of these but rather we will look at a couple of different ways of doing things after which you should understand things enough to off and have a yourself with the other options.
Our other option of format encoding besides XML is binary. Our example above modified shows us how easy we can do this as follows:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Serialization.Formatters.Binary;
namespace ConsoleSerialisation
{
public class Program
{
static void Main(string[] args)
{
Person person = new Person();
person.FirstName = "Sarah";
person.SurName = "Smith";
person.Age = 28;
BinaryFormatter binaryFormatter = new BinaryFormatter();
binaryFormatter.Serialize(Console.OpenStandardOutput(), person);
Console.Read();
}
}
[Serializable]
public class Person
{
public string FirstName
{
get;
set;
}
public string SurName
{
get;
set;
}
public int Age
{
get;
set;
}
}
}
Our output when we run the above program should look like this:
![](/images/tutorialimages/SerialiseBinaryOutput.gif)
Ok so now lets have a look at this in more detail with an explanation of the code.
We shall build a Windows Forms application to do some tinkering. This will look like this so ahead and create this form.
![](/images/tutorialimages/SerialiseForm.gif)
If you want to cheat and can't be bothered to do all this then feel free to use my designer generated code as follows:
namespace SerialisationTest
{
partial class MainForm
{
///<summary>
/// Required designer variable.
///</summary>
private System.ComponentModel.IContainer components = null;
///<summary>
/// Clean up any resources being used.
///</summary>
///<param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
///<summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
///</summary>
private void InitializeComponent()
{
this.FirstNameLabel = new System.Windows.Forms.Label();
this.AgeLabel = new System.Windows.Forms.Label();
this.SurNameLabel = new System.Windows.Forms.Label();
this.FirstName = new System.Windows.Forms.TextBox();
this.SurName = new System.Windows.Forms.TextBox();
this.Age = new System.Windows.Forms.NumericUpDown();
this.SaveButton = new System.Windows.Forms.Button();
this.XmlFormat = new System.Windows.Forms.RadioButton();
this.BinaryFormat = new System.Windows.Forms.RadioButton();
this.LoadButton = new System.Windows.Forms.Button();
this.DataPath = new System.Windows.Forms.TextBox();
this.DataPathLabel = new System.Windows.Forms.Label();
((System.ComponentModel.ISupportInitialize)(this.Age)).BeginInit();
this.SuspendLayout();
//
// FirstNameLabel
//
this.FirstNameLabel.AutoSize = true;
this.FirstNameLabel.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.FirstNameLabel.Location = new System.Drawing.Point(76, 41);
this.FirstNameLabel.Name = "FirstNameLabel";
this.FirstNameLabel.Size = new System.Drawing.Size(80, 20);
this.FirstNameLabel.TabIndex = 0;
this.FirstNameLabel.Text = "Firstname";
//
// AgeLabel
//
this.AgeLabel.AutoSize = true;
this.AgeLabel.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.AgeLabel.Location = new System.Drawing.Point(118, 146);
this.AgeLabel.Name = "AgeLabel";
this.AgeLabel.Size = new System.Drawing.Size(38, 20);
this.AgeLabel.TabIndex = 1;
this.AgeLabel.Text = "Age";
//
// SurNameLabel
//
this.SurNameLabel.AutoSize = true;
this.SurNameLabel.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.SurNameLabel.Location = new System.Drawing.Point(82, 90);
this.SurNameLabel.Name = "SurNameLabel";
this.SurNameLabel.Size = new System.Drawing.Size(74, 20);
this.SurNameLabel.TabIndex = 2;
this.SurNameLabel.Text = "Surname";
//
// FirstName
//
this.FirstName.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.FirstName.Location = new System.Drawing.Point(162, 38);
this.FirstName.Name = "FirstName";
this.FirstName.Size = new System.Drawing.Size(232, 26);
this.FirstName.TabIndex = 3;
//
// SurName
//
this.SurName.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.SurName.Location = new System.Drawing.Point(162, 90);
this.SurName.Name = "SurName";
this.SurName.Size = new System.Drawing.Size(232, 26);
this.SurName.TabIndex = 5;
//
// Age
//
this.Age.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.Age.Location = new System.Drawing.Point(162, 144);
this.Age.Maximum = new decimal(new int[] {
125,
0,
0,
0});
this.Age.Minimum = new decimal(new int[] {
1,
0,
0,
0});
this.Age.Name = "Age";
this.Age.Size = new System.Drawing.Size(79, 26);
this.Age.TabIndex = 6;
this.Age.Value = new decimal(new int[] {
1,
0,
0,
0});
//
// SaveButton
//
this.SaveButton.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.SaveButton.Location = new System.Drawing.Point(162, 241);
this.SaveButton.Name = "SaveButton";
this.SaveButton.Size = new System.Drawing.Size(84, 32);
this.SaveButton.TabIndex = 7;
this.SaveButton.Text = "Save";
this.SaveButton.UseVisualStyleBackColor = true;
this.SaveButton.Click += new System.EventHandler(this.Save_Click);
//
// XmlFormat
//
this.XmlFormat.AutoSize = true;
this.XmlFormat.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.XmlFormat.Location = new System.Drawing.Point(162, 302);
this.XmlFormat.Name = "XmlFormat";
this.XmlFormat.Size = new System.Drawing.Size(60, 24);
this.XmlFormat.TabIndex = 8;
this.XmlFormat.TabStop = true;
this.XmlFormat.Text = "XML";
this.XmlFormat.UseVisualStyleBackColor = true;
//
// BinaryFormat
//
this.BinaryFormat.AutoSize = true;
this.BinaryFormat.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.BinaryFormat.Location = new System.Drawing.Point(228, 302);
this.BinaryFormat.Name = "BinaryFormat";
this.BinaryFormat.Size = new System.Drawing.Size(71, 24);
this.BinaryFormat.TabIndex = 9;
this.BinaryFormat.TabStop = true;
this.BinaryFormat.Text = "Binary";
this.BinaryFormat.UseVisualStyleBackColor = true;
//
// LoadButton
//
this.LoadButton.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.LoadButton.Location = new System.Drawing.Point(252, 241);
this.LoadButton.Name = "LoadButton";
this.LoadButton.Size = new System.Drawing.Size(84, 32);
this.LoadButton.TabIndex = 10;
this.LoadButton.Text = "Load";
this.LoadButton.UseVisualStyleBackColor = true;
this.LoadButton.Click += new System.EventHandler(this.Load_Click);
//
// DataPath
//
this.DataPath.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.DataPath.Location = new System.Drawing.Point(162, 194);
this.DataPath.Name = "DataPath";
this.DataPath.Size = new System.Drawing.Size(335, 26);
this.DataPath.TabIndex = 12;
//
// DataPathLabel
//
this.DataPathLabel.AutoSize = true;
this.DataPathLabel.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.DataPathLabel.Location = new System.Drawing.Point(46, 194);
this.DataPathLabel.Name = "DataPathLabel";
this.DataPathLabel.Size = new System.Drawing.Size(110, 20);
this.DataPathLabel.TabIndex = 11;
this.DataPathLabel.Text = "Data File Path";
//
// MainForm
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(525, 362);
this.Controls.Add(this.DataPath);
this.Controls.Add(this.DataPathLabel);
this.Controls.Add(this.LoadButton);
this.Controls.Add(this.BinaryFormat);
this.Controls.Add(this.XmlFormat);
this.Controls.Add(this.SaveButton);
this.Controls.Add(this.Age);
this.Controls.Add(this.SurName);
this.Controls.Add(this.FirstName);
this.Controls.Add(this.SurNameLabel);
this.Controls.Add(this.AgeLabel);
this.Controls.Add(this.FirstNameLabel);
this.Name = "MainForm";
this.Text = "Serialisation Test";
this.Load += new System.EventHandler(this.MainForm_Load);
((System.ComponentModel.ISupportInitialize)(this.Age)).EndInit();
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.Label FirstNameLabel;
private System.Windows.Forms.Label AgeLabel;
private System.Windows.Forms.Label SurNameLabel;
private System.Windows.Forms.TextBox FirstName;
private System.Windows.Forms.TextBox SurName;
private System.Windows.Forms.NumericUpDown Age;
private System.Windows.Forms.Button SaveButton;
private System.Windows.Forms.RadioButton XmlFormat;
private System.Windows.Forms.RadioButton BinaryFormat;
private System.Windows.Forms.Button LoadButton;
private System.Windows.Forms.TextBox DataPath;
private System.Windows.Forms.Label DataPathLabel;
}
}
Ok so the purpose of this program should now be fairly apparent. We are going to enter a Firstname, Surname and Age into the form fields and then click Save. This will create an instance of a Person class with property values corresponding to those entered on the form. The person object will then be serialised to disk either as XML or binary depending on which radio button is selected on the form. The location of the file we will serialise to is specified in the Data File Path textbox.
So we can then run the program and save a person object. We can then close the program and start it up again and this time click on the Load button. Providing we have selected the correct format then the object should be deserialised and the form fields repopulated.
So the first bit of code we need to write is a Serialise method which will take a person object as a parameter and serialise it.
This method should look like this:
private void Serialise(Person person)
{
if (XmlFormat.Checked)
{
XmlSerializer serialiser = new XmlSerializer(person.GetType());
using (StreamWriter writer = new StreamWriter(DataPath.Text))
{
serialiser.Serialize(writer, person);
writer.Close();
}
}
else if (BinaryFormat.Checked)
{
BinaryFormatter binaryFormatter = new BinaryFormatter();
using (FileStream output = File.OpenWrite(DataPath.Text))
{
binaryFormatter.Serialize(output, person);
}
}
else
{
MessageBox.Show("Please select a data format", "Selection error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
Now in order for any of this to work we need to specify some namespaces with using statements. These will be as follows:
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
using System.Xml.Serialization;
So this method has 3 parts or blocks of code. The first bit is executed if the XmlFormat radio button is ticked. This creates an instance of the class which does our xml serialisation which is the XmlSerialiser class. This takes a type as a parameter to the constructor. We then call the Serialize method of this object which in this example takes 2 parameters - there are many overloads availabel here so feel free to have a look and try those out too. The first is a stream which we get by creating a new StreamWriter pointing at our file. The second parameter is the obejct we want to serialise and that's pretty much all there is to it. If this works ok then you should now have a text file on disk which should look something like this:
![](/images/tutorialimages/XmlPerson.gif)
The second block of code will run if we have ticked the radio button to choose a binary format.
This then creates an instance of a BinaryFormatter object which comes from the System.Runtime.Serialization.Formatters.Binary namespace.
We then create a Stream object by using the static method of the System.IO.File class. This is then used as one of the arguments of the Serialise method of our BinaryFormatter object, the other parameter being the object we want to serialise. And that's all there is to it. The Serialisation methods and classes are so well encapsulated that it really is very little work for us to use.
If you run this sucessfully and then open the file in notepad then you should see something like this:
The third block of code is just to give the user a message if they have not selected one of the radio buttons to specify which format they want to serialise in.
Ok so now we need to look at the reverse process.
First we can write the code for the event handler when our Load button is clicked. This should look like this:
private void Load_Click(object sender, EventArgs e)
{
Person person = null;
try
{
if (XmlFormat.Checked)
{
person = DeSerialiseFromXML();
}
else if (BinaryFormat.Checked)
{
person = DeSerialiseFromBinary();
}
else
{
MessageBox.Show("Please select a data format", "Selection error", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
FirstName.Text = person.FirstName;
SurName.Text = person.SurName;
Age.Value = (decimal)person.Age;
}
catch(Exception ex)
{
MessageBox.Show("There was a problem trying to deserialise as follows: " + Environment.NewLine + ex.Message, "DeSerialisation error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
So all we are doing here is as follows:
- Create an instance of a Person
- Set that instance to the object returned from a method called DeSerialiseFromXML if the XML Format radio button is ticked
- Set that instance to the object returned from a method called DeSerialiseFromBinary if the Binary Format radio button is ticked
- Set the form fields to the corresponding properties of our person object
- Show the user an error message if they do not choose a format
- Wrap the whole lot in a try catch to show a handled exception message should somethign wrong
Something will in fact wrong if we try and deserialise an xml file using our binary deserialising code and vice versa.
Ok so now we can write our two methods to do our XML deserialisation and our Binary deserialisation. Both of these need to return an instance of a Person class. The first of these methods looks like this:
private Person DeSerialiseFromBinary()
{
BinaryFormatter binaryFormatter = new BinaryFormatter();
Person person = new Person();
using (FileStream input = File.OpenRead(DataPath.Text))
{
person = (Person)binaryFormatter.Deserialize(input);
}
return person;
}
So all is does is create an instance of our BinaryFormatter object (It's the same object used for serialisation and deserialisation). We then create an instance of a person class. The Deserialize method of the BinaryFormater object needs a stream as a parameter so we create one by using the OpenRead method of teh System.IO.File class which points to our file.
We then set our person object to the object returned from the Deserialize method. Since this method can work with many objects it of course doesn't return a person object bit returns a System.Object and so we need to cast it to a person adn then we simply return this person object to teh code which called this method.
The XML method is as follows:
private Person DeSerialiseFromXML()
{
XmlSerializer serialiser = new XmlSerializer(typeof(Person));
Person person = new Person();
using (FileStream input = File.OpenRead(DataPath.Text))
{
person = (Person)serialiser.Deserialize(input);
}
return person;
}
This works pretty much the same (from a logic point of view) as the previous method so this should be self explanatory.
There are some further and interesting things you can do with serialisation. Xml serialisation for exampel can be used in many way. For exampel you may wish to serialise some object in xml and then use that as part of some other xml document rather than just serialising and deserialising it.
We will now have a quick look at some things you can and can't do with serialisation.
With binary serialisation for example it is possible to serialise many object to the same stream in sequential order.
To perform these exercises you will need to add two new classes to your project. These classes are called Animal and Car and look like this:
Animal Class
[Serializable]
public class Animal
{
public Animal()
{
}
public Animal(string animalType, int numberOfLegs)
{
this.AnimalType = animalType;
this.NumberOfLegs = numberOfLegs;
}
public string AnimalType
{
get;
set;
}
public int NumberOfLegs
{
get;
set;
}
}
Car Class
[Serializable]
public class Car
{
public Car()
{
}
public Car(string make, bool automaticTransmission)
{
this.Make = make;
this.AutomaticTransmission = automaticTransmission;
}
public string Make
{
get;
set;
}
public bool AutomaticTransmission
{
get;
set;
}
}
If you now create the following and execute it you will see that we are able to serialise a person, animla and car and then deserilse them back as long as we do it in exactly the same order though. To test this try chnaging the deserialisation order so that we try and retrieve a person then a car and then an animal and when you run this you will see that an exception is raised.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Serialization;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
namespace ConsoleSerialisation
{
public class Program
{
static void Main(string[] args)
{
BinaryFormatter binaryFormatter = new BinaryFormatter();
Person sarah = new Person("Sarah", "Smith", 28);
Animal cat = new Animal("Cat", 2);
Car mondeo = new Car("Ford", false);
using (FileStream output = File.OpenWrite(@"c:\test.bin"))
{
binaryFormatter.Serialize(output, sarah);
binaryFormatter.Serialize(output, cat);
binaryFormatter.Serialize(output, mondeo);
}
Person sarahCopy = new Person();
Animal catCopy = new Animal();
Car mondeoCopy = new Car();
using (FileStream input = File.OpenRead(@"c:\test.bin"))
{
sarahCopy = (Person)binaryFormatter.Deserialize(input);
catCopy = (Animal)binaryFormatter.Deserialize(input);
mondeoCopy = (Car)binaryFormatter.Deserialize(input);
}
Console.WriteLine("sarahCopy is named {0} {1} and is {2} years old", sarahCopy.FirstName, sarahCopy.SurName, sarahCopy.Age);
Console.WriteLine("catCopy is a type of {0} and has {1} legs", catCopy.AnimalType, catCopy.NumberOfLegs);
Console.WriteLine("mondeoCopy is a {0} and the value of AutomaticTransmission is {1}", mondeoCopy.Make, mondeoCopy.AutomaticTransmission);
Console.Read();
}
}
}
Out of interest, lets have a look at the binary file created by the above program. You can either open this in Notepad but you will get a much better look at the contents if you open this with Visual Studio when you will see something like this:
So looking at this we can clearly see the 3 chunks of data representing our objects.
Now lets have a look at doing a simillar thing but with an xml format.
We will first start off with just code to do the serialisation. Create your program as follows and run it.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Serialization;
using System.IO;
namespace ConsoleSerialisation
{
public class Program
{
static void Main(string[] args)
{
using (StreamWriter writer = new StreamWriter(@"c:\test.xml"))
{
Person sarah = new Person("Sarah", "Smith", 28);
Animal cat = new Animal("Cat", 2);
Car car = new Car("Ford", false);
XmlSerializer personSerialiser = new XmlSerializer(typeof(Person));
XmlSerializer animalSerialiser = new XmlSerializer(typeof(Animal));
XmlSerializer carSerialiser = new XmlSerializer(typeof(Car));
personSerialiser.Serialize(writer, sarah);
animalSerialiser.Serialize(writer, cat);
carSerialiser.Serialize(writer, car);
}
}
}
}
You should then be able to open the xml file and it should look like this:
<?xml version="1.0" encoding="utf-8"?>
<Person xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<FirstName>Sarah</FirstName>
<SurName>Smith</SurName>
<Age>28</Age>
</Person><?xml version="1.0" encoding="utf-8"?>
<Animal xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<AnimalType>Cat</AnimalType>
<NumberOfLegs>2</NumberOfLegs>
</Animal><?xml version="1.0" encoding="utf-8"?>
<Car xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Make>Ford</Make>
<AutomaticTransmission>false</AutomaticTransmission>
</Car>
So straight away you should be able that this is problematic as our xml document is not well formed. For starters it has 3 xml lines - the lines which normally appear just once at the top of an xml file. Even if there was only one of these then the document would still not be well formed since it would have no top level root node.
So this approach to multiple object serialisation is not an option. You would need to serialise each object into it's own stream or file. Alternativeley if you create another composite object which had an instance of a person, animal and car as properties then you could just serialise that one object.
So thats enough for now. There are many more interesting things you can do with serialisation so and have a tinker.
Any comments are welcome via the form below.