If you can't find what you need using the site search on the toolbar above, or if you need more detailed help or just need to be pointed in the right direction, post your question to the newly opened kadaitcha.cx forums. Membership is free.

How to Serialise & Deserialise (Persist & Depersist) Custom Classes

There are innumerable Visual Studio objects that support serialisation; ToolBox, ListView, TreeNode and many others support a .Serialize method, and with a BinaryFormatter or XmlSerializer, you can serialise a single Visual Studio object or a connected network of Visual Studio objects, all the way up to entire sets of database tables and even whole datasets. However the catch is in the words "Visual Studio object"; it isn't obvious how you might go about persisting and depersisting your own custom classes. This article will show you how.

Walkthrough

ISerializable Interface

Your custom object needs to implement the ISerializable Interface in order to control its own serialisation and deserialisation. If that sounds ominous, don't worry. Controlling your own serialisation only means telling the IFormatter what data types your object implements, and what that data is; it doesn't mean you have to implement stream readers/writers and XML parsers and do all the hack work yourself.

When you perform serialisation on your custom class, your chosen VS formatter will interrogate your custom object for certain helper methods and extract serialisation data, which is then used by the IFormatter interface. Setting up custom serialisation is very straightforward, though it can get a bit boring for very large objects.

  1. Import System.Runtime.Serialization and System.Security.Permissions

  2. Mark the class with the Serializable attribute

  3. Implement the ISerializable interface

Imports System.Runtime.Serialization
Imports System.Security.Permissions

<Serializable()> _
Public Class cSampleClass

    Implements ISerializable

Implementing the ISerializable Interface

Your object needs a GetObjectData method. In the example code that follows, assume a custom class with four string properties named Message, InReplyTo, Subject and References:

    <SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter:=True)> _
    Public Sub GetObjectData(ByVal info As SerializationInfo, _
                             ByVal context As StreamingContext) _
                             Implements ISerializable.GetObjectData

          ' [...]

    End Sub

Inside your GetObjectData method, you populate the SerializationInfo with data that represents your object's properties, i.e. the names of the properties and their values. For example:

    <SecurityPermissionAttribute(SecurityAction.Demand, _
                                 SerializationFormatter:=True)> _
    Public Sub GetObjectData(ByVal info As SerializationInfo, _
                         ByVal context As StreamingContext) _
                         Implements ISerializable.GetObjectData

        info.AddValue("Message", Me.Message)
        info.AddValue("InReplyTo", Me.InReplyTo)
        info.AddValue("Subject", Me.Subject)
        info.AddValue("References", Me.References)

    End Sub

That's all there is to it, well, for serialisation anyway.

Supporting Deserialisation

To allow your object to be deserialised, implement a simple constructor that takes SerializationInfo and StreamingContext as parameters. When your object is deserialised, the formatter will call your object's constructor with the SerializationInfo and StreamingContext data; all you need do is move the SerializationInfo into your object's properties:

    Protected Sub New(ByVal info As SerializationInfo, _
                      ByVal context As StreamingContext)

        Me.Message = info.GetString("Message")
        Me.InReplyTo = info.GetString("InReplyTo")
        Me.Subject = info.GetString("Subject")
        Me.References = info.GetString("References")

    End Sub

That's all there is to serialising and deserialising your own objects.

Supported Data Types

SerializationInfo supports boolean, 8, 16, 32 and 64-bit signed and unsigned integers, Unicode character, DateTime, decimal, double-precision and single-precision floating-point values.

Arrays

There is nothing special about setting up the SerializationInfo for arrays in your object's GetobjectData method, however the deserialisation constructor code shown immediately above requires a different method be called for arrays:

Given this code in GetObjectData for an array of integers named SomeIntegers:

info.AddValue("SomeIntegers", Me.SomeIntegers)

We would require this line in the constructor to implement deserialisation of the integer array:

Me.SomeIntegers = info.GetValue("SomeIntegers", GetType(Integer()))

Persist the Object

The code to put the custom class into a file on disk is very straightforward:

  1. Open a FileStream

  2. Dump the object straight into the fileStream using a BinaryFormatter

    ' Persists an object to a file on disk.
    Public Sub PersistObject(ByVal FileName As String, ByRef obj As Object)

        Dim BinarySerialiser As New BinaryFormatter
        Dim fs As FileStream = New FileStream(FileName, FileMode.Create)

        Try
            ' Serialise the object to filestream
            BinarySerialiser.Serialize(fs, obj)
            fs.Flush()
        Catch ex As SerializationException
            Throw ex
        Finally
            fs.Close()
        End Try

    End Sub

Depersist the Object

If you intend to write a general-purpose routine to handle deserialisation of multiple object types then getting the object back off the disk is isn't as straightforward as getting it onto the disk because the type of object being depersisted needs to be known. You can either have the caller handle the casting of the depersisted object into its correct type or you can handle the casting in the routine that depersists the object. In the code sample below, the DepersistObject method handles the object cast:

    Private Sub GetClientMessage()

        Dim o As New cClientMessage
        Dim Serialiser As New cSerialiser

        o = Serialiser.DepersistObject("msg.obj", cSerialiser.ObjectTypes.ClientMessage)

    End Sub

    ' [...]

   
Enum ObjectTypes As Integer
        ClientMessage
    End Enum

    Public Function DepersistObject(ByVal FileName As String, _
                                   
ByVal
oType As Integer) As Object

        ' Open the file
        Dim fs As New FileStream(FileName, FileMode.Open)
        Dim Formatter As New BinaryFormatter
        ' The object returned to the caller
        Dim obj As New Object

        ' Create the DataTable from the stream
        Select Case oType
            Case ObjectTypes.ClientMessage
                obj = DirectCast(Formatter.Deserialize(fs), cClientMessage)
            ' [...]
        End Select

        ' Clean up
        fs.Close()

        Return obj

    End Function

Handling the conversion at the calling level simplifies the DepersistObject method, but it also means that you will have calls to DirectCast spread throughout your code, which can make debugging more difficult, though the choice of implementation is yours:

    Private Sub GetClientMessage()

        Dim o As New cClientMessage
        Dim Serialiser As New cSerialiser

        o = DirectCast(Serialiser.DepersistObject("msg.obj"), cClientMessage)

    End Sub

    Public Function DepersistObject(ByVal FileName As String) As Object

        ' Open the file
        Dim fs As New FileStream(FileName, FileMode.Open)
        Dim Formatter As New BinaryFormatter
        ' The object returned to the caller
        Dim obj As New Object

        ' Clean up
        fs.Close()

        Return obj

    End Function

Download Sample Project

All code examples on this site have been developed for .Net Framework 3.5