(Print this page)

Object Serialization in VB.Net
Published date: Tuesday, July 1, 2003
On: Moer and Éric Moreau's web site

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: 

  • Shallow serialization: this type of serialization persists only public read-write property values of a class. The XMLSerializer and the WebServices use this technique. 
  • Deep serialization: this type of serialization persists all variables of an object. Deep serialization also serializes the object graph (the complete hierarchy). The BinaryFormatter, the SoapFormatter, and the .Net Remoting use this technique. 

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
Some of you may have noticed that the shallow serialization cannot be used on this class because it doesn't expose any read-write public properties.

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
Then, we can add code to serialize the class. In my case, I have added this code to the test form. It is as simple as creating a stream (I create a FileStream here), instantiating a BinaryFormatter object and calling the Serialize method:
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
To deserialize data, is about as easy. Again, we need to create a stream (to read data this time), we also need a BinaryFormatter instance and we need an object to store the deserialize data (this is why I create the objNewEmployee object):
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
Cloning an object With all we have seen so far, adding a clone (not a second pointer to the same instance – an independent copy of the object) feature to our class is really easy. We don't even need to use a file stream. We can use a memory stream!

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
Then, from the test form, you need to add this code to call the Clone method:
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
You will notice that this class also has the <Serializable()> attribute. If you forget to specify it and you don't mark the member as non-serializable, you will receive a runtime error when you will try to serialize the parent object.

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: 

  • Specify that you are implementing the ISerializable interface;
  • Write a method implementing the GetObjectData method; 
  • Provide a specific constructor. 

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
In the destination class, we need a special constructor (New method) having a specific signature. Here it is:
Private Sub New(ByVal Info As SerializationInfo, _
                ByVal Context As StreamingContext)
    With Info
        mstrFullName = .GetString("FullName")
        mintAge = .GetInt32("Age")
    End With
End Sub
This constructor will be called automatically when the deserialization will take place. You will notice that the constructor is using Get[type] method with the names you specified into the GetOjectData of the source class. The sequence in which you retrieve the variables is not important but the type you are using is very important. Another very important detail is the FullTypeName property that was set in the source class. If this property is different from the name, an exception will be raised.

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.


(Print this page)