In my first article on serialization, I discussed how to make a custom object serialized, and walked through an example of where serialization might be useful. In my example, we were storing stats for players on our baseball league on our laptop, serializing them, and moving them to a PC after the game. Now we are going to extend our previous example a little bit and I’ll demonstrate other possibilities with serialization.

Let’s take our Player object from the last article and extend it. We were tracking the player name, at-bats, hits, runs, RBI, and home runs. What if we wanted to add batting average to the object? We could serialize it, but why waste the space? This value is calculated from existing members, so we don’t really want to waste the space adding this to the serialization. In this example, just storing the batting average value and serializing it isn’t a big deal, but in a situation where you are worried about space and you aer serializing thousands (or even millions) of records, this can be useful. One way we can modify our class it to make batting average a non-serializable field, and then just restore it when we deserialize it on our PC.


// Adding the Serializable attribute to our class is all
// we have to do in order to enable serialization for
// our object. Implementing the IDeserializationCallback
// interface allows us to calculate the average after
// the object has been deserialized. That way we
// don't have to waste space serializing the average field.
[Serializable]
public class Player : IDeserializationCallback
{
/*-------------------------------------------------------------*/
// Private data members.
private int _atBats;
private int _hits;
private int _homeRuns;
private int _rbi;
private int _runs;
private string _name;

// Marking a data member as NonSerialized
// means that it will be ignored in the
// serialization process.
[NonSerialized]
private double _average;

/*-------------------------------------------------------------*/
// These are our public properties.
public int AtBats
{
get { return _atBats; }
set { _atBats = value; }
}
/*-------------------------------------------------------------*/
public int Hits
{
get { return _hits; }
set { _hits = value; }
}
/*-------------------------------------------------------------*/
public int HomeRuns
{
get { return _homeRuns; }
set { _homeRuns = value; }
}
/*-------------------------------------------------------------*/
public int RBI
{
get { return _rbi; }
set { _rbi = value; }
}
/*-------------------------------------------------------------*/
public int Runs
{
get { return _runs; }
set { _runs = value; }
}
/*-------------------------------------------------------------*/
public string Name
{
get { return _name; }
set { _name = value; }
}
/*-------------------------------------------------------------*/
public double Average
{
get { return _average; }
set { _average = value; }
}
/*-------------------------------------------------------------*/
public Player()
{

}
/*-------------------------------------------------------------*/
// This method must exist because we are implementing
// the IDeserializationCallback interface. This is called
// after the object has been deserialized.
// I am multiplying by 1.0 to make sure average is treated
// as a double since I'm dividing int by int.
public void OnDeserialization(object sender)
{
_average = (_atBats == 0) ? 7 : 1.0 * _hits / _atBats;
}
/*-------------------------------------------------------------*/
}


By implementing the IDeserializationCallback interface I can compute values in my obect after serialization without having to actually waste the space serializing them. This interface is in the System.Runtime.Serialization namespace. Another thing worth mentioning, but not really applicable to this example is the fact that you can mark fields as optional. You do this using [OptionalField] in C# the same way [NonSerialized] is used up above. This can be very useful if you have multiple versions that need to be compatable. If the new version uses a new field but you still want to be able to deserialize old objects the new field can be marked optional.

Another method of serialization is to use the System.XML.Serialization namespace. XML has it’s advantages and disadvantages. A major advantage is that it is a text standard, and you will find libraries that can handle XML parsing in pretty much any development platform that you work on. This allows you to serialize objects and process them in applications on any operating system. Another advantage is that you can read the serialized data in a text editor. When you open one of the serialized .dat files we created in the first article in Notepad, you will see that you can’t really read the information. This isn’t the case with XML since they are stored as text.

XML does have some limitations however. You can only serialize public data, so our class above would have to be modified. Your class must also have a constructor that doesn’t take any parameters to be serialized in this way. This is an example of how we might modify our Player class and serialize it with XML.


// This class can be used to serialize data using XML.
// All data members that are to be serialized are
// public, and the constructor has no parameters.
public class XMLPlayer
{
public int AtBats;
public int Hits;
public int HomeRuns;
public int RBI;
public int Runs;
public string Name;

public XMLPlayer()
{

}
}

public static void XMLSerialize()
{
string fileName;
XMLPlayer p1;

// Sample XMLPlayer object.
p1 = new XMLPlayer();
p1.AtBats = 4;
p1.Hits = 1;
p1.Runs = 2;
p1.RBI = 3;
p1.HomeRuns = 1;
p1.Name = "Jimmy Smith";

// Declare our filestream and XMLSerializer.
FileStream fStream;
XmlSerializer serializer;

// Use the name to come up with an XML filename.
fileName = p1.Name.Replace(' ', '_') + ".xml";

// Create our XML file to hold the serialized data.
fStream = new FileStream(fileName, FileMode.Create);

// Instantiate our serializer. Notice that I passed
// in what kind of object was being serialized.
serializer = new XmlSerializer(typeof(XMLPlayer));

// Serialize our object.
serializer.Serialize(fStream, p1);

// Close the file stream.
fStream.Close();
}


The result of this serialization produces a text file that is much more readable than something formatted using a BinaryFormatter.



xmlns:xsd="http://www.w3.org/2001/XMLSchema">
4
1
1
3
2
Jimmy Smith



You can also control how the XML is formatted to conform to any XML schema that you need to. This is done using XML Serialization attributes. For instance if we wanted to make our player name appear as an attribute rather than an element, we could modify our class to look something like this:


// This class can be used to serialize data using XML.
// All data members that are to be serialized are
// public, and the constructor has no parameters.
// I have declared XMLPlayer to be the XML Root.
[XmlRoot ("XMLPlayer")]
public class XMLPlayer
{
public int AtBats;
public int Hits;
public int HomeRuns;
public int RBI;
public int Runs;

// You can declare a data member
// to be an XML attribute like this.
[XmlAttribute] public string Name;

public XMLPlayer()
{

}
}


This produces an XML file that looks like this.



xmlns:xsd="http://www.w3.org/2001/XMLSchema"
Name="Jimmy Smith">
4
1
1
3
2



Serialization is necessary if you ever want to tranfer data between applications. I hope that this 2 part article helped demonstrate some ways to accomplish this task.

kick it on DotNetKicks.com