XMLシリアライズできないケースに対応する。Dictionaryクラスをシリアライズする。IXmlSerializable。

アトリビュートを付与しているインスタンスであればシリアライズできますが、インスタンスによっては失敗するケースがあります。

サンプルコードXMLSerializerTest.zip 直

■Dictinaryクラス


System.InvalidOperationException はハンドルされませんでした。
Message="型 'WindowsApplication1.Item' を反映中にエラーが発生しました。"
Message="IDictionary が実装されているため、型 System.Collections.Generic.Dictionary`2[[System.String, mscorlib, Version=2.0.0.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089],[System.Decimal, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]] のメンバ
WindowsApplication1.Item.PriceList はシリアル化できません。"

このエラーが発生するのはDictionaryクラスがXMLシリアライズの対象外のためです。標準では対応していないので、IXmlSerializableインタフェースを継承してXMLシリアライズするコードを実装します。


Public Class SerializableDictionary(Of T, U)
Inherits Dictionary(Of T, U)
Implements IXmlSerializable

Public Function GetSchema() As System.Xml.Schema.XmlSchema Implements System.Xml.Serialization.IXmlSerializable.GetSchema
Return Nothing
End Function

Public Sub ReadXml(ByVal reader As System.Xml.XmlReader) Implements System.Xml.Serialization.IXmlSerializable.ReadXml

Dim serializer As XmlSerializer = New XmlSerializer(GetType(KeyValueItem))

reader.ReadStartElement()

Try
Do While reader.NodeType <> Xml.XmlNodeType.EndElement
Dim item As KeyValueItem = serializer.Deserialize(reader)
Me.Add(item.Key, item.Value)
Loop
Finally
reader.ReadEndElement()
End Try

End Sub

Public Sub WriteXml(ByVal writer As System.Xml.XmlWriter) Implements System.Xml.Serialization.IXmlSerializable.WriteXml

Dim ns As New XmlSerializerNamespaces
ns.Add(String.Empty, String.Empty)

Dim serializer As XmlSerializer = New XmlSerializer(GetType(KeyValueItem))

For Each key In Keys

Dim item As New KeyValueItem

item.Key = key
item.Value = Me.Item(key)

serializer.Serialize(writer, item, ns)

Next

End Sub

_
_
Public Class KeyValueItem
_
Public Key As T
_
Public Value As U
End Class

End Class

Dictionaryなのでジェネリックを使用した実装となっています。ペアとなるクラスをKeyValueItemクラスとしてシリアライズ可能なクラスとして定義します、XmlRootおよびXmlElement属性を使ってXMLに出力される際の要素名を指定しておきます。指定しないと、KeyValueItemStringDecimalなどと残念な要素名で出力されてしまいます。

ReadXmlメソッドがデシリアライズXMLファイルの読み込み)WriteXmlメソッドがシリアライズXMLファイルの書き込み)にそれぞれ対応します。

ReadXmlメソッドでは、要素の先頭から(ReadStartElement)要素の最後まで(ReadEndElement)までデシリアライズを繰り返します。WriteXmlメソッドでは、インスタンスの値をKeyValueのペア毎にシリアライズします。空の名前空間を作ってシリアライズに指定しておきます。指定しないとKeyValueのペア毎に名前空間が出力されてしまいます。

それと、GetSchemaメソッドですがMicrosoftのマニュアルに以下のように記載されているのでNothingを返すようにしておきます。

このメソッドは予約されているため、使用しないでください。IXmlSerializable インターフェイスを実装するときは、このメソッドから nullNothingnullptrnull 参照 (Visual Basic では Nothing) 参照 (Visual Basic では Nothing) を返す必要があります。

検証用のコードです。


Imports System.IO
Imports System.Runtime.Serialization
Imports System.Xml.Serialization
Imports System.Data

_
Public Class Item
Public ShopName As String
Public PriceList As New SerializableDictionary(Of String, Decimal)
Public ShopCode As String
End Class

Public Class SerializableDictionary(Of T, U)
Inherits Dictionary(Of T, U)
Implements IXmlSerializable

Public Function GetSchema() As System.Xml.Schema.XmlSchema Implements System.Xml.Serialization.IXmlSerializable.GetSchema
Return Nothing
End Function

Public Sub ReadXml(ByVal reader As System.Xml.XmlReader) Implements System.Xml.Serialization.IXmlSerializable.ReadXml

Dim serializer As XmlSerializer = New XmlSerializer(GetType(KeyValueItem))

reader.ReadStartElement()

Try
Do While reader.NodeType <> Xml.XmlNodeType.EndElement
Dim item As KeyValueItem = serializer.Deserialize(reader)
Me.Add(item.Key, item.Value)
Loop
Finally
reader.ReadEndElement()
End Try

End Sub

Public Sub WriteXml(ByVal writer As System.Xml.XmlWriter) Implements System.Xml.Serialization.IXmlSerializable.WriteXml

Dim ns As New XmlSerializerNamespaces
ns.Add(String.Empty, String.Empty)

Dim serializer As XmlSerializer = New XmlSerializer(GetType(KeyValueItem))

For Each key In Keys

Dim item As New KeyValueItem

item.Key = key
item.Value = Me.Item(key)

serializer.Serialize(writer, item, ns)

Next

End Sub

_
_
Public Class KeyValueItem
_
Public Key As T
_
Public Value As U
End Class

End Class

Public Class StartUp

Public Shared Sub Main()

Dim ns As New XmlSerializerNamespaces
ns.Add(String.Empty, String.Empty)

Dim item As New Item

item.ShopName = "my store"

item.PriceList.Add("book", 1000)
item.PriceList.Add("pencil", 100)

item.ShopCode = "1000"

Dim serializer As New XmlSerializer(GetType(Item))

Dim sw As New StreamWriter("dictionary.xml")

serializer.Serialize(sw, item, ns)

sw.Close()

Dim sr As New StreamReader("dictionary.xml")

item = CType(serializer.Deserialize(sr), Item)

sr.Close()

End Sub

End Class