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 IXmlSerializablePublic Function GetSchema() As System.Xml.Schema.XmlSchema Implements System.Xml.Serialization.IXmlSerializable.GetSchema
Return Nothing
End FunctionPublic 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 TryEnd 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 ClassEnd 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 ClassPublic Class SerializableDictionary(Of T, U)
Inherits Dictionary(Of T, U)
Implements IXmlSerializablePublic Function GetSchema() As System.Xml.Schema.XmlSchema Implements System.Xml.Serialization.IXmlSerializable.GetSchema
Return Nothing
End FunctionPublic 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 TryEnd 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 ClassEnd 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