.NET – Serialization, part II

Posted by Jack Altiere on August 15th, 2006

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

.NET – Serialization

Posted by Jack Altiere on August 9th, 2006

If you’re worked on any sort of application that had to transfer data between applications, you have probably had to serialize data. Serialization is the process of converting objects to a collection of bytes that can be interpreted by the application receiving the data. The application receiving the data can be another application on the same computer, but more often is an application on a remote computer. You can get a more formal definition here. I’m going to talk about serialization as it applies specifically to the .NET 2.0 Framework.

The framework has support for serialization built in with the BinaryFormatter and SoapFormatter classes. I’m going to start with the BinaryFormatter class. The first thing I need to point about about using a BinaryFormatter is that it’s only readable by applications written in the .NET Framework. Another item worth mentioning is that objects formatted using the BinaryFormatter have a better chance of being blocked by a firewall, so if you are transferring data to remote computers, this is something to consider.

Let’s get started with an example. Let’s say we want to build an application to track statistics for our baseball league. We have a laptop that we will bring to the game, and a server at home that will hold all of the data, so there will be 2 parts to our application. The basic idea of our application is to collect the game data using our laptop, and move it to our server when we get home. Serialization is one way to accomplish this. Here is an example player class we could use: (in C#)


// Adding the Serializable attribute to our class
// allows us to serialize it
[Serializable]
public class Player
{
/*-------------------------------------------------------------*/
// Private data members.
private int _atBats;
private int _hits;
private int _homeRuns;
private int _rbi;
private int _runs;
private string _name;
/*-------------------------------------------------------------*/
// 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; }
}
/*-------------------------------------------------------------*/
// Constructor
public Player()
{

}
/*-------------------------------------------------------------*/
}



All we had to do to be able to serialize our class is to put the [Serializable] attribute above it. It doesn’t get much easier than that! Now lets say our application has a List<Player> declared. We decide that we want to serialize a separate file for each player on our list. We could also just serialize the whole list, but we’ll do it this way instead. I have to throw out my standard disclaimer…..I’m going light on the error handling to make the example more clear.


string fileName;
// This is our list of players on the team.
List teamList = new List();

// Assume that our application has compiled game stats
// for each player in the list and that we are ready
// to serialize the data. For example purposes, I'll
// just hard code a few players and add them to the list.
Player p1 = new Player();
p1.AtBats = 4;
p1.Hits = 1;
p1.HomeRuns = 0;
p1.RBI = 2;
p1.Runs = 1;
p1.Name = "John Doe";
teamList.Add(p1);

Player p2 = new Player();
p2.AtBats = 4;
p2.Hits = 3;
p2.HomeRuns = 1;
p2.RBI = 2;
p2.Runs = 1;
p2.Name = "Jimmy Smith";
teamList.Add(p2);

// Declare our formatter and filestream;
BinaryFormatter formatter;
FileStream fStream;

// Loop through each player and serialize the data to a file.
foreach (Player currentPlayer in teamList)
{
// I'm going to just use their name for the filename,
// replacing spaces with underscores.
// In reality we would check for invalid characters
// here also, etc.
fileName = currentPlayer.Name.Replace(' ', '_') + ".dat";

// This creates our data file.
fStream = new FileStream(fileName, FileMode.Create);

// Create our binary formatter object.
formatter = new BinaryFormatter();

// Serialize our player data.
formatter.Serialize(fStream, currentPlayer);

// Close our data file.
fStream.Close();
}



That’s it. We now have our 2 player objects serialized into separate files that we can move to our PC and deserialize. One thing I should point out, using a BinaryFormatter requires the System.Runtime.Serialization.Formatters.Binary namespace to be included in your project. If you open up one of the files that we generated in Notepad, it will look like a bunch of gibberish, but that’s OK, we’ll be able to read it just fine. Here is an example of some code that would read these files on our PC at home. I’m assuming the files were dropped into a directory called import that was in the same directory as our application. This function is one way to read these files and put them back into List form.


public List Deserialize()
{
// Our function will return a list of player objects.
List playerList = new List();
Player currentPlayer;
string importPath, fileName;

// Declare our FileStream and BinaryFormatter
FileStream fStream;
BinaryFormatter formatter;

// Set up the import directory path.
importPath = Directory.GetCurrentDirectory()
+ Path.DirectorySeparatorChar + "import";

DirectoryInfo importDirectory = new DirectoryInfo(importPath);
foreach (FileInfo file in importDirectory.GetFiles())
{
// Set up the correct filename.
fileName = importPath + Path.DirectorySeparatorChar
+ file.Name;

// Open our data file for reading.
fStream = new FileStream(fileName, FileMode.Open);

// Create our binary formatter object.
formatter = new BinaryFormatter();

// Deserialize our data stream into a Player object.
currentPlayer = (Player)formatter.Deserialize(fStream);

// Close our file stream.
fStream.Close();

// Add the player to our list.
playerList.Add(currentPlayer);
}

// Return the list.
return playerList;
}



Another type of serialization uses the SoapFormatter class. The syntax of using this is almost identical to using the BinaryFormatter class since the both implement the IRemotingFormatter interface, so I won’t get into specific examples, but I will point out a few things that may be worth knowing. This class is less efficient than the BinaryFormatter class, but it is a better choice if there are firewall concerns. The reason it is less efficient is because it is intended to be used with web services. Serialization with SOAP can make the serialized objects 3 or 4 times as big as the same object serialized with a BinaryFormatter. Keep in mind though that something serialized with a SoapFormatter can be read by applications on different platform because of the SOAP standard, so that is something to consider.

That wraps up the first article on serialization. My next one will deal with things such as choosing fields to skip in the serialization process, choosing optional fields in an object, as well as XML specific formatting via libraries in the System.Xml.Serialization namespace.

kick it on DotNetKicks.com

Downloads

Twitter Updates

    Top Commentators

    • No commentators.

    Copyright © 2007 Jack Altiere. All rights reserved.