Programmers often need to save the value of properties of classes. The .Net Framework offers an alternate storage solution in cases where a database might not be practical. The serialization is the ability to save object instances to disk and then reload them later. The serialization treats all of an object's data as a single unit.
As you will discover while reading this column, it is really easy to serialize a complex structure of class and the classes nested in them (often called the object graph).
Streams are used to store the result of the serialization. This means that you can store the result into any kind of System.IO.Stream like a FileStream, a MemoryStream, a CryptoStream, etc.
Types of serialization
The .Net Framework offers two types of serialization:
This column will focus of the deep (or binary) serialization type.
Marking an object as Serializable
You need to specify that a class can be serialized. By default, classes are not serializable. You simply need to add the <Serializable()> attribute in front of your class declaration to enable the deep serialization. Another (more advanced) technique consists in implementing the ISerializable interface. This technique will also be introduced in this column.
In some case, you may want some properties or variables not to be persisted. It is possible to prevent properties or variables to be serialized simply by adding the <NonSerialized()> attribute in front of the member that you don't want to serialize.
Serialize
As I said in the introduction, the serialized data are stored into a byte stream, an object that derives from System.IO.Stream. The serialization is handled by a method called Serialize (located into the System.Runtime.Serialization.Formatters.Binary namespace).
Deserialize
The deserialization is handled by a method called Deserialize (also located into the System.Runtime.Serialization.Formatters.Binary namespace). This operation takes the byte stream and tries to fill object members.
Enough talking, let's do some code!
I just want to warn you that only key portions of code are listed here. I invite you to use the link from the end of this article to download the complete source code.
Figure 1: The test form.
First we need to create a class. The small class used to demo the serialization concept is simply exposing a constructor that accepts three arguments. Three public read-only properties are available (notice that an age is passed in one of the constructor and that only the age is exposed). A ToString method is also available to easily display results.
Option Strict On Public Class clsEmployeeSimple Private mdtmDOB As Date Private mstrFirstName As String Private mstrLastName As String Public Sub New(ByVal pLName As String, _ ByVal pFName As String, _ ByVal pDOB As Date) mstrLastName = pLName mstrFirstName = pFName mdtmDOB = pDOB End Sub Public ReadOnly Property Age() As Long Get Return DateDiff(DateInterval.Year, mdtmDOB, Now) End Get End Property Public ReadOnly Property FirstName() As String Get Return mstrFirstName End Get End Property Public ReadOnly Property LastName() As String Get Return mstrLastName End Get End Property Public Overrides Function ToString() As String Return FirstName & " " & LastName & _ " is " & Age.ToString & "." End Function End Class
Next, we need to mark the class as serializable. This is as simple as adding <Serializable()> in front of the class signature:
<Serializable()> _ Public Class clsEmployeeSimple
Private Sub btnBinarySave_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles btnBinarySave.Click Dim fs As Stream = New FileStream( _ "c:\temp\BinarySerialization.dat", _ FileMode.Create) Dim bf As BinaryFormatter = New BinaryFormatter() bf.Serialize(fs, mobjEmployee1) fs.Close() End Sub
Private Sub btnBinaryRead_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles btnBinaryRead.Click Dim objNewEmployee As clsEmployeeSimple Dim fs As Stream = New FileStream( _ "c:\temp\BinarySerialization.dat", _ FileMode.Open) Dim bf As BinaryFormatter = New BinaryFormatter() objNewEmployee = CType(bf.Deserialize(fs), clsEmployeeSimple) fs.Close() MessageBox.Show("We just read this employee data: " & _ Environment.NewLine & _ objNewEmployee.ToString) objNewEmployee = Nothing End Sub
To clone easily, add this method to the class (you will need to import System.IO and System.Runtime.Serialization.Formatters.Binary namespaces):
Public Function Clone() As clsEmployeeSimple Dim ms As Stream = New MemoryStream() Dim bf As BinaryFormatter = New BinaryFormatter() bf.Serialize(ms, Me) ms.Position = 0 Return CType(bf.Deserialize(ms), clsEmployeeSimple) End Function he clone method here
Private Sub btnBinaryClone_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles btnBinaryClone.Click Dim objClone As clsEmployeeSimple objClone = mobjEmployee1.Clone MessageBox.Show("We just read this employee data: " & _ Environment.NewLine & _ objClone.ToString) objClone = Nothing End Sub
Taking care of the children
As I said earlier, deep serialization can serialize the complete object hierarchy easily. To demonstrate it, add a child class to contain the child(ren) name and date-of-birth of employee's child(ren).
<Serializable()> _ Public Class clsChild Private mdtmDOB As Date Private mstrFirstName As String Public Sub New(ByVal pFName As String, _ ByVal pDOB As Date) mstrFirstName = pFName mdtmDOB = pDOB End Sub Public ReadOnly Property Age() As Long Get Return DateDiff(DateInterval.Year, mdtmDOB, Now) End Get End Property Public ReadOnly Property FirstName() As String Get Return mstrFirstName End Get End Property Public Overrides Function ToString() As String Return FirstName & " is " & Age.ToString & "." End Function End Class
The employee class has been modified to store the children collection into a HashTable (a private variable) and a AddChild method. The ToString method is also modified to display the number of children. For the sake of clarity, a completely new class (clsEmployeeWithChild) has been created into the downloadable project.
No modifications are required to serialize or to deserialize data. It is completely handled by the .Net Framework.
Implementing the ISerializable interface
From the beginning of this column, we let the Framework serialize and deserialize the object the way it wanted to do. It was very easy and very powerful considering we didn't write that much code!
However, you may more control on the way data is serialized. You have to do it by implementing the ISerializable interface. Because you want more control on the way data is serialized, you will be required to write code to specify it! You typically want to control the serialization to fit a particular format. Imagine that you have to share some data between two different classes.
Implementing this interface is still easy. It only requires three things:
If you are just like me, things become a lot clearer after reading some code! To demo this method, we will create two new classes nearly identical to the clsEmployeeSimple class. These new classes are named clsEmployeeAdvancedSource and clsEmployeeAdvancedDestination. These classes will be used to send data from the source class to the destination class that has a different interface. To do this, we need to customize the serialization and the deserialization processes.
The source class is just like the original class (with a FirstName, a LastName, and a Date-Of-Birth properties) to which a special method (named GetObjectData) has been added. Also, a line has been added telling the interface we are implementing (Implements ISerializable) on the line that follows the class name:
<Serializable()> _ Public Class clsEmployeeAdvancedSource Implements ISerializable
As I said earlier, the destination class has a different interface. It only has a FullName property and an Age property! This is why we will need to customize the serialization and deserialization processes.
In the source class, the GetObjectData method specifies a FullTypeName attribute in which you need to give the exact NameSpace and ClassName of the class that will deserialize this data. If you don't follow this rule, you won't be able to deserialize it. You also need to give pairs of name and value you want to serialize by using the AddValue method.
Here is the code of the GetObjectData method of the source class.
Public Sub GetObjectData(ByVal Info As SerializationInfo, _ ByVal Context As StreamingContext) _ Implements ISerializable.GetObjectData With Info .FullTypeName = _ "ObjectSerialization. clsEmployeeAdvancedDestination" .AddValue("FullName", FirstName & " " & LastName) .AddValue("Age", Age) End With End Sub
Private Sub New(ByVal Info As SerializationInfo, _ ByVal Context As StreamingContext) With Info mstrFullName = .GetString("FullName") mintAge = .GetInt32("Age") End With End Sub
Potential problems
The biggest problem I found to the automatic serialization is that the data file becomes unreadable when the structure of your class changes. To overcome this problem for newly added members, you need to implement the ISerializable interface. For other modifications you need to use BindToType method that let you create a kind of translator between your classes versions. See more detail on this topic on the MSDN site : SerializationBinder.BindToType Method.
Conclusion
As you have seen in this column, the automatic serialization is very powerful and still remains very easy to use! I hope you will remember this technique the next time you will have to persist object's data between sessions!
I hope you appreciated the topic and see you next month.