オブジェクトのXMLシリアル化

注意:
この文書は以前「.NETでいきまっしょい!」で公開していたものですが、公開以降メンテナンスされていません。 今や古い情報となった内容が記載されている場合があるのでご注意ください。

1.シリアル化とは


 シリアル化とはオブジェクトの値を保存できるようにすることをいいます。 また、XMLシリアル化することによって、オブジェクトの値(ただしパブリックプロパティ・パブリックフィールドの値に限る)をXMLファイルに書き出すことができます。 さらに、逆シリアル化によってシリアル化によって作成したXMLファイルからオブジェクトの状態を復元する事ができます。 最も簡単なシリアル化の例を見てみましょう。
最も簡単なシリアル化 (XMLシリアル化)
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
Imports System
Imports System.IO
Imports System.Xml
Imports System.Xml.Serialization
Imports System.Text

Module Module1

    Public Function Main(ByVal args() As String) As Integer

        ' XMLシリアル化の対象となるインスタンスを作成
        Dim aibon As New ClassForSerialization("加護亜依", 15, "AB")

        ' XmlSerializer インスタンスを作成
        Dim serializer As New XmlSerializer(aibon.GetType)

        ' XMLファイルを書き出すための StreamWriter インスタンスを作成
        Dim writer As New StreamWriter("E:\Serialization_00.xml", False, Encoding.UTF8)

        ' XMLシリアル化
        serializer.Serialize(writer, aibon)

        ' ファイルをクローズ
        writer.Close()

        Return 0

    End Function

End Module

' シリアル化の対象となるクラス
<Serializable()> _
Public Class ClassForSerialization

    ' シリアル化の対象となるメンバ
    Public Name As String
    Public Age As Integer
    Public BloodType As String

    ' XMLシリアル化には既定のコンストラクタが必要
    Public Sub New()
    End Sub

    ' コンストラクタ
    Public Sub New(ByVal Name As String, ByVal Age As Integer, ByVal BloodType As String)

        Me.Name = Name
        Me.Age = Age
        Me.BloodType = BloodType

    End Sub

End Class
出力結果
<?xml version="1.0" encoding="utf-8"?>
&l;ClassForSerialization xmlns:xsd="http://www.w3.org/2001/XMLSchema"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <Name>加護亜依</Name>
  <Age>15</Age>
  <BloodType>AB</BloodType>
</ClassForSerialization>
出力結果(XMLファイル)

 出力されたファイルを見る場合は上のリンクをクリックして下さい。 このファイルを見ればわかるように、XMLシリアル化によってClassForSerializationクラスの各メンバの値がXMLファイルに出力されています。 では、ソースコードの動作についてみてみます。
 まず、ClassForSerializationクラスを見ると、クラス宣言の一番始めに「<Serializable()>」という属性が付いています。 これはSerializableAttribute属性で、このクラスのインスタンスがシリアル化可能であることを示しています。 クラスの動作自体は問題ないと思います。 ただ、空のコンストラクタが宣言されているのは、シリアル化に際して既定のコンストラクタが必要なため意図的に宣言してあります(最初このことを知らずにいたので、何度やっても例外エラーが発生して困り果てていました)。
 つづいて、Main()メソッドではコメントに書いてあるとおりのことをやっています。 15行目では、コンストラクタで指定した型をシリアル化するためのXMLSerializerクラスのインスタンスを作成しています。 型情報を取り出すためにインスタンスのGetType()メソッドを呼び出しています。 18行目で出力ファイルのコードページをUTF-8に設定しているのは、XMLファイルがUTF-8で出力されるからです。 続いてシリアル化本番の21行目でファイルストリームとシリアル化対象を渡してやります。 これでシリアル化が終わるので最後にファイルストリームをクローズしてやります。
 このように、XMLシリアル化は簡単な手順で行うことができます。 続いて、シリアル化しただけではその能力の半分も使い切れていないので、逆シリアル化をしてみます。 逆シリアル化はその名の通りシリアル化の逆、つまりXMLファイルからクラスインスタンスの情報を復元します。
逆シリアル化
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
Imports System
Imports System.IO
Imports System.Xml
Imports System.Xml.Serialization
Imports System.Text

Module Module1

    Public Function Main(ByVal args() As String) As Integer

        ' オブジェクト変数を宣言
        Dim aibon As ClassForSerialization

        ' XmlSerializer インスタンスを作成
        Dim serializer As New XmlSerializer(GetType(ClassForSerialization))

        ' XMLファイルを読み出すための StreamReader インスタンスを作成
        Dim reader As New StreamReader("E:\Serialization_00.xml")

        ' XMLシリアル化
        aibon = serializer.Deserialize(reader)

        ' ファイルをクローズ
        reader.Close()

        ' インスタンスのフィールド値を出力
        Console.WriteLine("名前: {0} ({1}歳, {2}型)", aibon.Name, aibon.Age, aibon.BloodType)

        Return 0

    End Function

End Module

' シリアル化の対象となるクラス
<Serializable()> _
Public Class ClassForSerialization

    ' シリアル化の対象となるメンバ
    Public Name As String
    Public Age As Integer
    Public BloodType As String

    ' XMLシリアル化には既定のコンストラクタが必要
    Public Sub New()
    End Sub

    ' コンストラクタ
    Public Sub New(ByVal Name As String, ByVal Age As Integer, ByVal BloodType As String)

        Me.Name = Name
        Me.Age = Age
        Me.BloodType = BloodType

    End Sub

End Class
出力結果
名前: 加護亜依 (15歳, AB型)
Press any key to continue

 実行結果は見ての通りで、見事保存されている値が読み込まれています。 ソースコードは先ほどとほとんど変わらないので説明するまでもありませんが、唯一説明すべきは15行目のXmlSerializerのインスタンスを作成するコードだと思います。 ここでも逆シリアル化するために必要な型情報を取得するのですが、取得できるインスタンスがないのでクラス型から直接GetTypeキーワードにより取り出しています。 また、実際に逆シリアル化されたインスタンスは、21行目のように、Deserialize()メソッドの戻り値として帰ってきます。

2.シリアル化するために


 シリアル化を行うためにはいくつか考慮すべき点があります。 MSDNにそれらをまとめた文章があるので一部引用させてもらい、ここに示します。

・シリアル化されたデータに含まれるのは、クラスの構造とデータだけです。型の識別子やアセンブリの情報は含まれません。
・シリアル化できるのは、パブリック プロパティとパブリック フィールドだけです。非パブリック データをシリアル化する必要がある場合は、XML シリアル化ではなく BinaryFormatter クラスを使用します。
・クラスを XmlSerializer でシリアル化するには、そのクラスに既定のコンストラクタがあることが必要です。
・メソッドはシリアル化できません。


 シリアル化を行う際にはこの点を考慮しなければなりません。 実際に様々なスコープのプロパティ、フィールドをもったクラスを作成し、それをシリアル化した際に出力されるファイルを見てみることにします。
シリアル化されるメンバ、されないメンバ
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
Imports System
Imports System.IO
Imports System.Xml
Imports System.Xml.Serialization
Imports System.Text

Module Module1

    Public Function Main(ByVal args() As String) As Integer

        ' XMLシリアル化の対象となるインスタンスを作成
        Dim aibon As New ClassForSerialization("加護亜依", 15, "AB", New DateTime(1988, 2, 7), "奈良県")

        ' XmlSerializer インスタンスを作成
        Dim serializer As New XmlSerializer(aibon.GetType)

        ' XMLファイルを書き出すための StreamWriter インスタンスを作成
        Dim writer As New StreamWriter("E:\Serialization_01.xml", False, Encoding.UTF8)

        ' XMLシリアル化
        serializer.Serialize(writer, aibon)

        ' ファイルをクローズ
        writer.Close()

        Return 0

    End Function

End Module

' シリアル化の対象となるクラス
<Serializable()> _
Public Class ClassForSerialization

    ' パブリックフィールド
    Public Name As String
    Public Age As Integer

    ' プロテクティッドフィールド
    Protected m_BloodType As String

    ' プライベートフィールド
    Private m_BirthDate As DateTime
    Private m_BirthPlace As String

    ' パブリックプロパティ
    Public Property BirthDate() As DateTime
        Get
            Return m_BirthDate
        End Get
        Set(ByVal Value As DateTime)
            m_BirthDate = Value
        End Set
    End Property

    ' プライベートプロパティ
    Private Property BirthPlace() As String
        Get
            Return m_BirthPlace
        End Get
        Set(ByVal Value As String)
            m_birthplace = Value
        End Set
    End Property

    ' XMLシリアル化には既定のコンストラクタが必要
    Public Sub New()
    End Sub

    ' コンストラクタ
    Public Sub New(ByVal Name As String, ByVal Age As Integer, _
                    ByVal BloodType As String, ByVal BirthDate As DateTime, ByVal BirthPlace As String)

        Me.Name = Name
        Me.Age = Age
        Me.m_BloodType = BloodType
        Me.m_BirthDate = BirthDate
        Me.m_BirthPlace = BirthPlace

    End Sub

End Class
出力結果
<?xml version="1.0" encoding="utf-8"?>
<ClassForSerialization xmlns:xsd="http://www.w3.org/2001/XMLSchema"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <Name>加護亜依</Name>
  <Age>15</Age>
  <BirthDate>1988-02-07T00:00:00.0000000+09:00</BirthDate>
</ClassForSerialization>
出力結果(XMLファイル)

 このように、パブリックなスコープを持つフィールド及びプロパティのみがシリアル化されます。 そのほかの値はシリアル化の対象から除外されます。 また、DateTime型の値の出力結果を見てみると、ToString()メソッドによって得られる文字列とは異なる値が出力されています。

3.シリアル化の制御


 パブリックなプロパティとフィールドがシリアル化の対象になりますが、必ずしもこれら全てをシリアル化の対象としたくない場合があります。 このような場合には、属性を用いることでシリアル化の制御を行うことができます。 まず、シリアル化の対象から除外する属性を使用した例を見てみます。
シリアル化対象から除外する
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
Imports System
Imports System.IO
Imports System.Xml
Imports System.Xml.Serialization
Imports System.Text

Module Module1

    Public Function Main(ByVal args() As String) As Integer

        Dim aibon As New ClassForSerialization("加護亜依", 15, "AB", New DateTime(1988, 2, 7), "奈良県")
        Dim serializer As New XmlSerializer(aibon.GetType)
        Dim writer As New StreamWriter("E:\Serialization_02.xml", False, Encoding.UTF8)

        serializer.Serialize(writer, aibon)

        writer.Close()

        Return 0

    End Function

End Module

<Serializable()> _
Public Class ClassForSerialization

    Public Name As String
    Public Age As Integer
    <XmlIgnore()> Public BloodType As String
    <XmlIgnore()> Public BirthDate As DateTime
    Public BirthPlace As String

    Public Sub New()
    End Sub

    Public Sub New(ByVal Name As String, ByVal Age As Integer, _
                    ByVal BloodType As String, ByVal BirthDate As DateTime, ByVal BirthPlace As String)

        Me.Name = Name
        Me.Age = Age
        Me.BloodType = BloodType
        Me.BirthDate = BirthDate
        Me.BirthPlace = BirthPlace

    End Sub

End Class
出力結果
<?xml version="1.0" encoding="utf-8"?>
<ClassForSerialization xmlns:xsd="http://www.w3.org/2001/XMLSchema"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <Name>加護亜依</Name>
  <Age>15</Age>
  <BirthPlace>奈良県</BirthPlace>
</ClassForSerialization>
出力結果(XMLファイル)

 シリアル化対象からメンバを除外する場合は、XmlIgnoreAttribute属性を用います。 ソースコードから想像できるように、出力結果にはBloodType, BirthDateの値は出力されていないはずです。 上のリンクをクリックして出力ファイルを見てみて下さい。
 また、出力されるXMLファイルでの要素名を変更するにはXmlElementAttribute属性を用います。 この属性は要素名だけではなく、逆シリアライズ時における型の指定をすることもできます。 要素名を変更する場合の例が次のソースコードです。
要素名を変更する
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
Imports System
Imports System.IO
Imports System.Xml
Imports System.Xml.Serialization
Imports System.Text

Module Module1

    Public Function Main(ByVal args() As String) As Integer

        Dim aibon As New ClassForSerialization("加護亜依", 15, "AB", New DateTime(1988, 2, 7), "奈良県")
        Dim serializer As New XmlSerializer(aibon.GetType)
        Dim writer As New StreamWriter("E:\Serialization_03.xml", False, Encoding.UTF8)

        serializer.Serialize(writer, aibon)

        writer.Close()

        Return 0

    End Function

End Module

<Serializable()> _
Public Class ClassForSerialization

    <XmlElement("氏名")> Public Name As String
    <XmlElement("年齢")> Public Age As Integer
    <XmlElement("血液型")> Public BloodType As String
    <XmlElement("誕生日")> Public BirthDate As DateTime
    <XmlElement("出生地")> Public BirthPlace As String

    Public Sub New()
    End Sub

    Public Sub New(ByVal Name As String, ByVal Age As Integer, _
                    ByVal BloodType As String, ByVal BirthDate As DateTime, ByVal BirthPlace As String)

        Me.Name = Name
        Me.Age = Age
        Me.BloodType = BloodType
        Me.BirthDate = BirthDate
        Me.BirthPlace = BirthPlace

    End Sub

End Class
出力結果
<?xml version="1.0" encoding="utf-8"?>
<ClassForSerialization xmlns:xsd="http://www.w3.org/2001/XMLSchema"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <氏名>加護亜依</氏名>
  <年齢>15</年齢>
  <血液型>AB</血液型>
  <誕生日>1988-02-07T00:00:00.0000000+09:00</誕生日>
  <出生地>奈良県</出生地>
</ClassForSerialization>
出力結果(XMLファイル)

 要素名を変更する必要性がある状況として、要素名とメンバ名を変えたい場合、例えば要素名に使用することは許可される名前でメンバ名では許可されない名前を使用する場合などが挙げられます(この例では要素名を日本語文字にしていますが、VB.NETではメンバ名に日本語文字を使用することは可能です)。

4.配列のシリアル化


 いままでは単一のクラスをシリアル化してきましたが、続いて複数のデータを持つような配列におけるシリアル化を行ってみます。 ただ、配列を直接シリアル化することはできないので、何らかの方法でクラスに内包する必要があります。 ここでは配列をパブリックフィールドとして保持するクラスを用いることで、間接的に配列をシリアル化しています。
配列のシリアル化
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
Imports System
Imports System.IO
Imports System.Xml
Imports System.Xml.Serialization
Imports System.Text

Module Module1

    Public Function Main(ByVal args() As String) As Integer

        ' インスタンスを作成
        Dim aibon As New Musume("加護亜依", New DateTime(1988, 2, 7))
        Dim konkon As New Musume("紺野あさ美", New DateTime(1987, 5, 7))
        Dim nacchi As New Musume("安部なつみ", New DateTime(1981, 8, 10))

        ' インスタンスから配列を作成する
        Dim members() As Musume = {aibon, konkon, nacchi}

        ' 配列を内包するクラスを作成
        Dim musume As New MorningMusume()
        musume.Members = members

        ' シリアル化
        Dim serializer As New XmlSerializer(musume.GetType())
        Dim writer As New StreamWriter("E:\Serialization_04.xml", False, Encoding.UTF8)

        serializer.Serialize(writer, musume)

        writer.Close()

        Return 0

    End Function

End Module

' MorningMusumeクラス
<Serializable()> _
Public Class MorningMusume

    <XmlArrayItem(GetType(Musume))> _
    Public Members As Musume()

    Public Sub New()
    End Sub

End Class

' Musumeクラス
Public Class Musume

    Public Name As String
    Public Age As Integer
    Public BirthDate As DateTime

    Public Sub New()
    End Sub

    Public Sub New(ByVal Name As String, ByVal BirthDate As DateTime)

        Me.Name = Name
        Me.BirthDate = BirthDate

        ' 年齢って誕生日から計算できますね・・・
        Me.Age = DateTime.Now.Year - BirthDate.Year

        Dim birthDayInThisYear As New DateTime(DateTime.Now.Year, BirthDate.Month, BirthDate.Day)

        If birthDayInThisYear > DateTime.Now Then Me.Age -= 1

    End Sub

End Class
出力結果
<?xml version="1.0" encoding="utf-8"?>
<MorningMusume xmlns:xsd="http://www.w3.org/2001/XMLSchema"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <Members>
    <Musume>
      <Name>加護亜依</Name>
      <Age>15</Age>
      <BirthDate>1988-02-07T00:00:00.0000000+09:00</BirthDate>
    </Musume>
    <Musume>
      <Name>紺野あさ美</Name>
      <Age>15</Age>
      <BirthDate>1987-05-07T00:00:00.0000000+09:00</BirthDate>
    </Musume>
    <Musume>
      <Name>安部なつみ</Name>
      <Age>21</Age>
      <BirthDate>1981-08-10T00:00:00.0000000+09:00</BirthDate>
    </Musume>
  </Members>
</MorningMusume>
出力結果(XMLファイル)

 さらに、Object型の配列のように複数の型のデータが入る可能性のある場合は、属性でその入りうる型の情報を指定してやります。
配列のシリアル化
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
Imports System
Imports System.IO
Imports System.Xml
Imports System.Xml.Serialization
Imports System.Text

Module Module1

    Public Function Main(ByVal args() As String) As Integer

        Dim aibon As New Musume("加護亜依", New DateTime(1988, 2, 7), 4)
        Dim konkon As New Musume("紺野あさ美", New DateTime(1987, 5, 7), 5)
        Dim nacchi As New Musume("安部なつみ", New DateTime(1981, 8, 10), 1)
        Dim mikitty As New Girl("藤本美貴", New DateTime(1985, 2, 26))
        Dim ayaya As New Girl("松浦亜弥", New DateTime(1986, 6, 25))

        Dim members() As Girl = {aibon, konkon, nacchi, mikitty, ayaya}

        Dim hello As New HelloProject()
        hello.Members = members

        Dim serializer As New XmlSerializer(hello.GetType())
        Dim writer As New StreamWriter("E:\Serialization_05.xml", False, Encoding.UTF8)

        serializer.Serialize(writer, hello)

        writer.Close()

        Return 0

    End Function

End Module

' HelloProjectクラス
<Serializable()> _
Public Class HelloProject

    <XmlArrayItem(GetType(Girl)), _
     XmlArrayItem(GetType(Musume))> _
    Public Members As Girl()

    Public Sub New()
    End Sub

End Class

' Girlクラス
Public Class Girl

    Public Name As String
    Public Age As Integer
    Public BirthDate As DateTime

    Public Sub New()
    End Sub

    Public Sub New(ByVal Name As String, ByVal BirthDate As DateTime)

        Me.Name = Name
        Me.BirthDate = BirthDate

        Me.Age = DateTime.Now.Year - BirthDate.Year

        Dim birthDayInThisYear As New DateTime(DateTime.Now.Year, BirthDate.Month, BirthDate.Day)

        If birthDayInThisYear > DateTime.Now Then Me.Age -= 1

    End Sub

End Class

' Musumeクラス
Public Class Musume
    Inherits Girl

    Public Generation As Integer

    Public Sub New()
    End Sub

    Public Sub New(ByVal Name As String, ByVal BirthDate As DateTime, ByVal Generation As Integer)

        MyBase.New(Name, BirthDate)
        Me.Generation = Generation

    End Sub

End Class
出力結果
<?xml version="1.0" encoding="utf-8"?>
<MorningMusume xmlns:xsd="http://www.w3.org/2001/XMLSchema"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <Members>
    <Musume>
      <Name>加護亜依</Name>
      <Age>15</Age>
      <BirthDate>1988-02-07T00:00:00.0000000+09:00</BirthDate>
      <Generation>4</Generation>
    </Musume>
    <Musume>
      <Name>紺野あさ美</Name>
      <Age>15</Age>
      <BirthDate>1987-05-07T00:00:00.0000000+09:00</BirthDate>
      <Generation>5</Generation>
    </Musume>
    <Musume>
      <Name>安部なつみ</Name>
      <Age>21</Age>
      <BirthDate>1981-08-10T00:00:00.0000000+09:00</BirthDate>
      <Generation>1</Generation>
    </Musume>
    <Girl>
      <Name>藤本美貴</Name>
      <Age>18</Age>
      <BirthDate>1985-02-26T00:00:00.0000000+09:00</BirthDate>
    </Girl>
    <Girl>
      <Name>松浦亜弥</Name>
      <Age>16</Age>
      <BirthDate>1986-06-25T00:00:00.0000000+09:00</BirthDate>
    </Girl>
  </Members>
</MorningMusume>
出力結果(XMLファイル)

 これによって出力されたファイルを逆シリアル化するには次のコードの様に行います(クラスの設計は全く変えません)。
配列のシリアル化
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
Imports System
Imports System.IO
Imports System.Xml
Imports System.Xml.Serialization
Imports System.Text

Module Module1

    Public Function Main(ByVal args() As String) As Integer

        Dim hello As New HelloProject()
        Dim serializer As New XmlSerializer(hello.GetType())
        Dim reader As New StreamReader("E:\Serialization_05.xml")

        hello = serializer.Deserialize(reader)

        reader.Close()

        Dim girl As Girl

        For Each girl In hello.Members

            Console.Write("名前: {0} ({1}歳, 誕生日{2}) ", girl.Name, girl.Age, girl.BirthDate.ToLongDateString)

            If TypeOf girl Is Musume Then

                Console.WriteLine("第" + CType(girl, Musume).Generation.ToString() + "期メンバー")

            Else

                Console.Write(vbNewLine)

            End If

        Next

        Return 0

    End Function

End Module
出力結果
名前: 加護亜依 (15歳, 誕生日1988年2月7日) 第4期メンバー
名前: 紺野あさ美 (15歳, 誕生日1987年5月7日) 第5期メンバー
名前: 安倍なつみ (21歳, 誕生日1981年8月10日) 第1期メンバー
名前: 藤本美貴 (18歳, 誕生日1985年2月26日)
名前: 松浦亜弥 (16歳, 誕生日1986年6月25日)
Press any key to continue