EditOverview
From Wikipedia:
Serialization is the process of saving an object onto a storage medium (such as a file, or a memory buffer) or to transmit it across a network connection link in binary form. The series of bytes or the format can be used to re-create an object that is identical in its internal state to the original object (actually, a clone).
In the .NET framwork, an object can be serialized into simple XML, SOAP or a Binary format.
The various classes you can use as serializers are:
An additional format known as
JSON can be used with a 3rd party library. I've seen at least 2 open-source libraries that will do the same basic task with that format, but have not used either of them in a production app.
EditHow to choose a formatter
As you will see below, this comes down to speed, human readability and compatibility with SOAP standards. You must decide which of these is most important to your application. If speed is the most important, use the BinaryFormatter; otherwise, use the XMLFormatter unless you absolutely must have compatibility with a SOAP standard.
EditSpeed
Simple performance test (object with a 2 public fields serialized 10,000 times):
- SoapFormatter - 1.76 seconds
- XMLFormatter - 1.55 seconds
- BinaryFormatter - 0.11 seconds
Second test on a small list of 1000 objects executed 1000 times:
- SoapFormatter - n/a - This formatter is not compatible with Generics
- XMLFormatter - 3.45 seconds
- BinaryFormatter - 3.69 seconds
With individual objects, the BinaryFormatter is definitely faster than the other 2. With List objects, the performance difference is negligible.
EditSize of output
Using a simple class with 2 numeric fields:
- SoapFormatter - 683 bytes
- XMLFormatter - 203 bytes
- BinaryFormatter - 161 bytes
EditHuman Readability
The BinaryFormatter is not really human readable, so if that's a requirement, don't choose that formatter.
XML output of a simple class with 2 numeric values:
<?xml version="1.0"?>
<MyTestClass xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Num1>993929257</Num1>
<Num2>701858102</Num2>
</MyTestClass>
The class in SOAP format:
<SOAP-ENV:Envelope
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:clr="http://schemas.microsoft.com/soap/encoding/clr/1.0"
SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<SOAP-ENV:Body>
<a1:MyTestClass id="ref-1" xmlns:a1="http://schemas.microsoft.com/clr/nsassem/TestXmlSerializer/TestXmlSerializer%2C%20Version%3D1.0.0.0%2C%20Culture%3Dneutral%2C%20PublicKeyToken%3Dnull">
<Num1>1583791413</Num1>
<Num2>656474251</Num2>
</a1:MyTestClass>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
EditSample Code
EditInstantiating the formatter
For the XMLSerializer, you'll need to specify the type of the object you'll be serializing in the constructor:
Dim ser As New System.Xml.Serialization.XmlSerializer(myObject.GetType)
Dim ser As New System.Xml.Serialization.XmlSerializer(GetType(MyClassName))
Dim ser As New System.Xml.Serialization.XmlSerializer(GetType(List(of MyClassName)))
For the other 2, a simple constructor call is sufficient:
Dim ser As New System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
Dim ser As New System.Runtime.Serialization.Formatters.Soap.SoapFormatter
EditSetting up your object
Each class you serialize must have the
Serializable attribute. Each of the items in your class must also be defined with that attribute. In this case below,
Integer (along with all of the primitive types) is already defined with that attribute.
<Serializable()> Public Class MyTestClass
Public Num1 As Integer
Public Num2 As Integer
End Class
EditSerializing an object to a file
Assuming the serializer has been instantiated already as
ser.
Using fs As New IO.StreamWriter("c:\temp\myfilename.xml")
ser.Serialize(fs, myObject)
End Using
Do not serialize more than one object to the same stream unless you've implemented a method of reading back in each individual chunk of data. The Deserialize method is unable to handle multiple objects in a single stream automatically.
You can serialize an entire
List(of T) object into a stream and the deserializer will recreate the full
List object in one call to Deserialize.
EditDeserializing an object from a file
Assuming the serializer has been instantiated already as
ser.
Using fs As New IO.StreamReader("c:\temp\myfilename.xml")
myObject = CType(ser.Deserialize(fs), MyTestClass)
End Using
EditSerializing an object to a byte array
Assuming the serializer has been instantiated already as
ser.
Dim data() as Byte
Using fs As New IO.MemoryStream("c:\temp\myfilename.xml")
ser.Serialize(fs, myObject)
data = fs.ToArray
End Using
EditSerializing an object to a String
Assuming the serializer has been instantiated already as
ser.
Dim result as String
Using fs As New IO.MemoryStream("c:\temp\myfilename.xml")
ser.Serialize(fs, myObject)
result = Text.Encoding.Default.GetString(fs.ToArray)
End Using
EditControlling XML output
For the XMLFormatter, there are a variety of attributes that can be applied to your class that will generate and interpret different XML structures (for these examples, I'm going to remove the namespace attributes for readability):
Original class:
<Serializable()> Public Class MyTestClass
Public Num1 As Integer
Public Num2 As Integer
End Class
Resulting XML:
<?xml version="1.0"?>
<MyTestClass">
<Num1>0</Num1>
<Num2>0</Num2>
</MyTestClass>
Class with renamed element:
<Serializable()> Public Class MyTestClass
<Xml.Serialization.XmlElement("MainNum")> Public Num1 As Integer
Public Num2 As Integer
End Class
Resulting XML:
<?xml version="1.0"?>
<MyTestClass">
<MainNum>0</MainNum>
<Num2>0</Num2>
</MyTestClass>
Class with element mapped to an attribute instead:
<Serializable()> Public Class MyTestClass
<Xml.Serialization.XmlAttribute()> Public Num1 As Integer
<Xml.Serialization.XmlAttribute()> Public Num2 As Integer
End Class
<?xml version="1.0"?>
<MyTestClass Num1="0" Num2="0" />
EditWriting multiple objects to one file
This technique will work equally well with any of the serializer types, but since it writes the length of each block in binary, it's already lost it's "human readability". As such, the
BinaryFormatter is used here because of it's speed. This block (without the Console.Writeline) ran in 0.25 seconds using the
BinaryFormatter and 2.02 seconds using the
XMLSerializer.
Dim ser As New System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
Dim fn As String = "c:\temp\myfilename.dat"
IO.File.Delete(fn)
Using fs As IO.FileStream = IO.File.OpenWrite(fn)
For i As Integer = 0 To 9999
Dim tmp As New MyTestClass
tmp.LoadValues() 'fill the object with values
Using mem As New IO.MemoryStream
ser.Serialize(mem, tmp) 'serialize the data to a memory stream
Dim b1() As Byte = mem.ToArray 'extract the data as a byte array
fs.Write(BitConverter.GetBytes(b1.Length), 0, 4) 'write the length of the byte array
fs.Write(b1, 0, b1.Length) 'write the byte array
End Using
Next
End Using
Using fs As IO.FileStream = IO.File.OpenRead(fn)
Dim bDataLen(3) As Byte
Do
Dim n As Integer = fs.Read(bDataLen, 0, 4) 'read the length
If n < bDataLen.Length Then Exit Do 'no more data left
Dim dataLen As Integer = BitConverter.ToInt32(bDataLen, 0)
'create a byte array based on the data length
Dim b1() As Byte = CType(Array.CreateInstance(GetType(Byte), dataLen), Byte())
fs.Read(b1, 0, b1.Length)
Using mem As New IO.MemoryStream(b1) 'put the data into a memory stream
Dim tmp As MyTestClass = CType(ser.Deserialize(mem), MyTestClass) 'deserialize it
Console.WriteLine(tmp.ToString)
End Using
Loop
End Using