Introduction
The most samples of serialization and deserialization available in net are written for C# and use List<T>
general class.
This tip provides a way of serialization and deserialization of custom class which inherits from CollectionBase
and explains the reason of the following issues:
There was an error reflecting type 'xxx'. (System.InvalidOperationException)
You must implement a default accessor on 'xxx' because it inherits from ICollection. (System.InvalidOperationException)
Background
I assume that you've got basic knowledge about:
- how to write custom class
- how to create collection of classes
- how to use nested classes
- what serialization and deserialization does/means
In case you want to round out your knowledge, here is a collection of useful links:
- MSDN
- CodeProject articles/tips
Case Study
Let' say you need to write application which:
- can collect notes and each note uses custom
id
- provides a way to export data to XML file
- provides a way to import data from XML file
There is another requirement: XML file must fulfil schema requirements (*.xsd file):
<!---->
<xs:schema targetnamespace="urn:SoftwareDeveloperName"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:annotation>
<xs:documentation>
-- Copyright (C) 2014 (author: Your Name here). --
</xs:documentation>
</xs:annotation>
<xs:element name="MyData">
<xs:complextype>
<xs:sequence>
<xs:element type="xs:string" name="Version" fixed="1.0">
<xs:element name="Notes" minoccurs="1" maxoccurs="unbounded">
<xs:complextype>
<xs:sequence>
<xs:element name="Note" minoccurs="1" maxoccurs="unbounded">
<xs:complextype>
<xs:simplecontent>
<xs:extension base="xs:string">
<xs:attribute type="xs:integer" name="ID">
</xs:attribute>
</xs:extension>
</xs:simplecontent>
</xs:complextype>
</xs:element><!---->
</xs:sequence>
</xs:complextype>
</xs:element><!---->
</xs:element></xs:sequence>
</xs:complextype>
</xs:element><!---->
Sample data:
<!---->
<mydata>
<version>1.0</version>
<notes>
<note id="1">Some text</note>
<note id="2">Another text</note>
<note id="3">Hello kitty!</note>
<note id="4">Good bye, my love...</note>
</notes>
Till now the task looks trivial, true?
The Mystery of Success
The mystery of success is to properly define MyData
class.
Have a look at the below diagram:
As you can see, MyData
class has 2 fields:
Version
- string
property to store version information
Notes
- nested class, the collection of Note
class
There is a few other things to remember:
- Serialization and deserialization of object (class) via XMLSerializer is possible on publicly visible and read-write fields!
- You can ignore some fields or change its destination name by using attributes
- All publicly visible types cannot have parameters
- The
XmlSerializer
cannot serialize or deserialize: arrays of ArrayList or arrays of List(Of T)
- It's possible to serialize/deserialize collection even if its property is set as
ReadOnly
.
- The collection must have default accessor
Definition of Classes
Note
class:
<serializable()> _
Public Class Note
Private iId As Integer = 0
Private sText As String = String.Empty
<XmlAttribute("ID")> _
Public Property ID() As Integer
Get
Return iId
End Get
Set(value As Integer)
iId = value
End Set
End Property
<XmlText()> _
Public Property Text() As String
Get
Return sText
End Get
Set(value As String)
sText = value
End Set
End Property
End Class
The definition of Note
class is very simple. Its XML representation must be:
<note id="1">Some Text</note>
instead of:
<note><id>1</id><text>Some Text</text></note>
or:
<note id="1"><text>Some Text</text></note>
That's why ID
field has been defined as a XmlAttribute
of XML element (Note) and Text
has been defined as a XmlText
:
NotesCollection
class:
Partial Public Class MyData
Public Class NotesCollection
Inherits CollectionBase
Public Sub Add(ByVal oNote As Note)
List.Add(oNote)
End Sub
Public Sub Remove(ByVal index As Integer)
If index > Count - 1 Or index < 0 Then
Console.WriteLine("Index not valid!")
Else
List.RemoveAt(index)
End If
End Sub
Default Public ReadOnly Property Item(ByVal index As Integer) As Note
Get
Return CType(List.Item(index), Note)
End Get
End Property
End Class
End Class
Have a look at the definition of Item
property! It's default accessor to Note
class!
And finally the definition of MyData
class:
<serializable()> _
<XmlRoot("MyData")> _
Public Class MyData
Private oNotes As NotesCollection = New NotesCollection
Private sVersion As String = "1.0"
<XmlElement("Version")> _
Public Property Version As String
Get
Return sVersion
End Get
Set(value As String)
sVersion = value
End Set
End Property
<XmlArray("Notes"), XmlArrayItem("Note", GetType(Note))> _
Public ReadOnly Property Notes As NotesCollection
Get
Return oNotes
End Get
End Property
End Class
The most important for serialization and deserialization of Notes
property is this line:
<XmlArray("Notes"), XmlArrayItem("Note", GetType(Note))> _
It means: treat this property as an array of objects (XML elements).
Usage
Module Module1
Sub Main()
Const sFile As String = "D:\Notes.xml"
Dim oData As MyData = Nothing
Dim oNote As Note = Nothing
Dim ans As ConsoleKeyInfo = Nothing
oData = New MyData
For i As Integer = 1 To 26
oNote = New Note
With oNote
.ID = i
.Text = Chr(64 + i)
End With
oData.Notes.Add(oNote)
Next
Console.WriteLine("Starting serialization...")
SerializeMyData(oData, sFile)
Console.WriteLine("Do you want to see XML file (y|n)?")
ans = Console.ReadKey(True)
If ans.KeyChar = "y" Then Process.Start(sFile)
oData = Nothing oData = New MyData() Console.WriteLine("Starting deserialization...")
Console.WriteLine("")
oData = DeserializeMyData(oData, sFile)
Console.WriteLine("Ready!")
Console.WriteLine("")
Console.WriteLine("Version: {0}", oData.Version)
Console.WriteLine("Notes:")
For Each n As Note In oData.Notes
Console.WriteLine("ID: {0}; Text: {1}", n.ID, n.Text)
Next
Console.WriteLine("")
Console.WriteLine("Done!")
Console.WriteLine("Press any key to exit.")
Console.ReadKey()
End Sub
Private Sub SerializeMyData(ByVal obj As MyData, ByVal sFileName As String)
Dim serializer As XmlSerializer = Nothing
Dim fs As System.IO.FileStream = Nothing
Try
serializer = New XmlSerializer(obj.GetType())
fs = New System.IO.FileStream(sFileName, IO.FileMode.Create)
serializer.Serialize(fs, obj)
fs.Close()
Catch ex As Exception
Console.WriteLine(ex.Message)
Finally
If Not fs Is Nothing Then fs.Close()
fs = Nothing
serializer = Nothing
End Try
End Sub
Private Function DeserializeMyData(ByVal obj As MyData, ByVal sFileName As String) As MyData
Dim serializer As XmlSerializer = Nothing
Dim fs As System.IO.FileStream = Nothing
Try
serializer = New XmlSerializer(obj.GetType())
fs = New System.IO.FileStream(sFileName, IO.FileMode.Open)
obj = serializer.Deserialize(fs)
fs.Close()
Catch ex As Exception
Console.WriteLine(ex.Message)
Finally
If Not fs Is Nothing Then fs.Close()
fs = Nothing
serializer = Nothing
End Try
Return obj
End Function
End Module
Points of Interest
I hope that reading this tip saved your time against possible problems while writing your code.
Please ask if you have any questions.
History
- 2014-06-19 - Initial version