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.
-
Import System.Runtime.Serialization and System.Security.Permissions
-
Mark the class with the Serializable attribute
-
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:
-
Open a FileStream
-
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 | |||