SOAPシリアル化とバイナリシリアル化

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

0.シリアル化について


 オブジェクトの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
Option Strict On

Imports System
Imports System.IO
Imports System.Runtime.Serialization
Imports System.Runtime.Serialization.Formatters.Soap

Module MainModule

    Sub Main()

        ' シリアル化すべきインスタンス
        Dim mikitty As New ClassForSerialization("藤本美貴", 18, "A")

        ' ストリームをオープン
        Dim stream As New FileStream("serialization.xml", FileMode.Create)

        ' SOAPフォーマッタを作成
        Dim formatter As New SoapFormatter(Nothing, New StreamingContext(StreamingContextStates.File))

        ' シリアル化
        formatter.Serialize(stream, mikitty)

        ' ストリームをクローズ
        stream.Close()

    End Sub

End Module

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

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

    ' コンストラクタ
    Public Sub New(ByVal name As String, ByVal age As Integer, ByVal bloodType As String)

        MyClass.name = name
        MyClass.age = age
        MyClass.BloodType = bloodType

    End Sub

    ' ToString()メソッドのオーバーライド
    Overrides Function ToString() As String

        Return MyClass.name + "(" + MyClass.age.ToString() + "歳," + MyClass.BloodType + "型)"

    End Function

End Class
出力結果
<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:ClassForSerialization id="ref-1"
    xmlns:a1="http://schemas.microsoft.com/clr/nsassem/Serialization/Serialization%2C%20Version%3D1.0.1257.29895%2C%20Culture%3Dneutral%2C%20PublicKeyToken%3Dnull">
<name id="ref-3">藤本美貴</name>
<age>18</age>
<BloodType id="ref-4">A</BloodType>
</a1:ClassForSerialization>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
出力結果(XMLファイル)

 出力されたXMLファイルを開いてみればわかると思いますが、フィールドのスコープに関わらず、すべてシリアル化されています。 SoapFormatterコンストラクタで指定しているStreamingContext構造体はシリアル化されたオブジェクトの転送元・転送先を指定するためのものです。 ここではファイルに出力するため、StreamingContextStates.Filesを指定しています。 また、シリアル化されたオブジェクトを逆シリアル化するには次のようにします。
ソースコード
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
Sub Main()

    ' 逆シリアル化されるオブジェクト
    Dim mikitty As ClassForSerialization

    ' ストリームをオープン
    Dim stream As New FileStream("serialization.xml", FileMode.Open)

    ' SOAPフォーマッタを作成
    Dim formatter As New SoapFormatter(Nothing, New StreamingContext(StreamingContextStates.File))

    ' シリアル化
    mikitty = CType(formatter.Deserialize(stream), ClassForSerialization)

    ' ストリームをクローズ
    stream.Close()

    ' 結果を出力
    Console.WriteLine(mikitty)

End Sub
出力結果
藤本美貴(18歳,A型)

2.バイナリシリアル化


 バイナリシリアル化は、その名の通りバイナリ形式でシリアル化します。 シリアル化に際して、インスタンスのメモリ内における実際のビット配置がシリアル化されます。 SOAPシリアル化と比べて非常に高速にシリアル化・逆シリアル化を行うことができます。
ソースコード
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
Option Strict On

Imports System
Imports System.IO
Imports System.Runtime.Serialization
Imports System.Runtime.Serialization.Formatters.Binary

Module MainModule

    Sub Main()

        ' シリアル化すべきインスタンス
        Dim mikitty As New ClassForSerialization("藤本美貴", 18, "A")

        ' ストリームをオープン
        Dim stream As New FileStream("serialization.bin", FileMode.Create)

        ' バイナリフォーマッタを作成
        Dim formatter As New BinaryFormatter(Nothing, New StreamingContext(StreamingContextStates.File))

        ' シリアル化
        formatter.Serialize(stream, mikitty)

        ' ストリームをクローズ
        stream.Close()

    End Sub

End Module

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

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

    ' コンストラクタ
    Public Sub New(ByVal name As String, ByVal age As Integer, ByVal bloodType As String)

        MyClass.name = name
        MyClass.age = age
        MyClass.BloodType = bloodType

    End Sub

    ' ToString()メソッドのオーバーライド
    Overrides Function ToString() As String

        Return MyClass.name + "(" + MyClass.age.ToString() + "歳," + MyClass.BloodType + "型)"

    End Function

End Class
出力結果(serialization.bin)
出力結果(serialization.bin)

 SOAPでの逆シリアル化同様に、シリアル化されたオブジェクトを逆シリアル化するには次のようにします。 基本的に変わっているところはフォーマッタにBinaryFormatterを指定しているところだけです。
ソースコード
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
Sub Main()

    ' 逆シリアル化されるオブジェクト
    Dim mikitty As ClassForSerialization

    ' ストリームをオープン
    Dim stream As New FileStream("serialization.bin", FileMode.Open)

    ' バイナリフォーマッタを作成
    Dim formatter As New BinaryFormatter(Nothing, New StreamingContext(StreamingContextStates.File))

    ' シリアル化
    mikitty = CType(formatter.Deserialize(stream), ClassForSerialization)

    ' ストリームをクローズ
    stream.Close()

    ' 結果を出力
    Console.WriteLine(mikitty)

End Sub
出力結果
藤本美貴(18歳,A型)

 このように、SOAPシリアル化・バイナリシリアル化のいずれでも適切にシリアル化・逆シリアル化が行われ、任意の時点でインスタンスを復元することができます。

3.NonSerialized属性


 SOAPシリアル化・バイナリシリアル化では、フィールドのスコープに関係なくすべてのフィールドがシリアル化の対象となっています。 しかし、場合によってはシリアル化したくないフィールドもでてくると思います。 そのような場合にはNonSerialized属性を使用します。 この属性は、シリアル化したくないフィールドの宣言部に指定します。
 次のコードは先ほどのクラスを多少変えたものです。 誕生日をメンバとして保持するために年齢はこの誕生日から計算すればよいため、このフィールドはシリアル化しないことにします。 シリアル化の方法は先ほど紹介した方法と変わりません。
ソースコード
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
' シリアル化の対象となるクラス
<Serializable()> _
Class ClassForSerialization

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

    ' シリアル化の対象外
    <NonSerialized()> Private m_Age As Integer

    Public ReadOnly Property Age() As Integer
        Get
            ' 誕生日から年齢を計算
            m_Age = DateTime.Now.Year - BirthDate.Year

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

            If birthDayInThisYear > DateTime.Now Then m_Age -= 1

            Return m_Age
        End Get
    End Property

    ' コンストラクタ
    Public Sub New(ByVal name As String, ByVal birthDate As Date, ByVal bloodType As String)

        MyClass.Name = name
        MyClass.BirthDate = birthDate
        MyClass.BloodType = bloodType

    End Sub

    ' ToString()メソッドのオーバーライド
    Overrides Function ToString() As String

        Return MyClass.Name + "(" + MyClass.BirthDate.ToShortDateString() + "生まれ," + _
               MyClass.Age.ToString() + "歳," + MyClass.BloodType + "型)"

    End Function

End Class
出力結果
<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:ClassForSerialization id="ref-1"
    xmlns:a1="http://schemas.microsoft.com/clr/nsassem/Serialization/Serialization%2C%20Version%3D1.0.1257.29895%2C%20Culture%3Dneutral%2C%20PublicKeyToken%3Dnull">
<name id="ref-3">藤本美貴</name> 
<age>18</age> 
<BloodType id="ref-4">A</BloodType> 
</a1:ClassForSerialization>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>

出力結果(XMLファイル)

 上のファイルはこのクラスをシリアル化した場合の出力結果の例です。 これを逆シリアル化し、ToString()メソッドを呼び出してメンバの値を確認すると、次のようになります。
出力結果
藤本美貴(1985/02/26生まれ,18歳,A型)

4.ISerializableインターフェイスによるシリアル化のカスタマイズ


 ISerializableインターフェイスを使用すると、シリアル化の動作を制御できます。 ISerializableインターフェイスにはGetObjectData()というメソッドが一つだけあるので、独自のシリアル化処理を行うには、対象のクラスにこのメソッドを実装するだけです。 このメソッドでは、シリアル化に関する情報(シリアル化する値と、それに関連づける名前)を追加します。 また、これとは別に二つのコンストラクタを用意する必要があります。
 一つは、インスタンスを生成するために必要な既定のコンストラクタで、もう一つはシリアル化情報を元にインスタンスの各フィールドの値を初期化するための暗黙のコンストラクタです。 これらをすべて実装し、独自のシリアル化を行うためのクラスのサンプルを次に示します。 基本的には先ほどから使用してきたクラスと同じ構造ですが、シリアル化に関する追加的なコードに注目してください。
ソースコード
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
' シリアル化の対象となるクラス
<Serializable()> _
Class ClassForSerialization

    ' ISerializableをインプリメント
    Implements ISerializable

    ' 各フィールド
    Public Name As String
    Public BloodType As String
    Public BirthDate As Date
    Private m_Age As Integer

    Public ReadOnly Property Age() As Integer
        Get
            ' 誕生日から年齢を計算
            m_Age = DateTime.Now.Year - BirthDate.Year

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

            If birthDayInThisYear > DateTime.Now Then m_Age -= 1

            Return m_Age
        End Get
    End Property

    ' 通常のコンストラクタ
    Public Sub New(ByVal name As String, ByVal birthDate As Date, ByVal bloodType As String)

        MyClass.Name = name
        MyClass.BirthDate = birthDate
        MyClass.BloodType = bloodType

    End Sub

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

    ' 逆シリアル化に必要な暗黙のコンストラクタ
    Private Sub New(ByVal info As SerializationInfo, ByVal context As StreamingContext)

        ' シリアル化情報から値を逆シリアル化された値を取得します
        MyClass.Name = info.GetString("名前")
        MyClass.BloodType = info.GetString("血液型")
        MyClass.BirthDate = info.GetDateTime("誕生日")

    End Sub

    ' GetObjectData()メソッドの実装
    Public Sub GetObjectData(ByVal info As SerializationInfo, ByVal context As StreamingContext) Implements ISerializable.GetObjectData

        ' シリアル化情報に、シリアル化に際して値に関連づけられる名称と、その値を追加します
        info.AddValue("名前", MyClass.Name)
        info.AddValue("血液型", MyClass.BloodType)
        info.AddValue("誕生日", MyClass.BirthDate)

    End Sub

    ' ToString()メソッドのオーバーライド
    Overrides Function ToString() As String

        Return MyClass.Name + "(" + MyClass.BirthDate.ToShortDateString() + "生まれ," + MyClass.Age.ToString() + "歳," + MyClass.BloodType + "型)"

    End Function

End Class
出力結果
<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:ClassForSerialization id="ref-1"
    xmlns:a1="http://schemas.microsoft.com/clr/nsassem/Serialization/Serialization%2C%20Version%3D1.0.1257.29895%2C%20Culture%3Dneutral%2C%20PublicKeyToken%3Dnull">
<名前 id="ref-3">藤本美貴</名前>
<血液型 id="ref-4">A</血液型>
<誕生日 xsi:type="xsd:dateTime">1985-02-26T00:00:00.0000000+09:00</誕生日>
</a1:ClassForSerialization>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>

出力結果(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
' シリアル化
Sub Serialize()

    ' シリアル化されるオブジェクト
    Dim mikitty As New ClassForSerialization("藤本美貴", New Date(1985, 2, 26), "A")

    ' ストリームをオープン
    Dim stream As New FileStream("serialization.xml", FileMode.Create)

    ' SOAPフォーマッタを作成
    Dim formatter As New SoapFormatter(Nothing, New StreamingContext(StreamingContextStates.File))

    ' シリアル化
    formatter.Serialize(stream, mikitty)

    ' ストリームをクローズ
    stream.Close()

End Sub

' 逆シリアル化
Sub Deserialize()

    ' 逆シリアル化されたオブジェクト
    Dim mikitty As ClassForSerialization

    ' ストリームをオープン
    Dim stream As New FileStream("serialization.xml", FileMode.Open)

    ' SOAPフォーマッタを作成
    Dim formatter As New SoapFormatter(Nothing, New StreamingContext(StreamingContextStates.File))

    ' 逆シリアル化
    mikitty = CType(formatter.Deserialize(stream), ClassForSerialization)

    ' ストリームをクローズ
    stream.Close()

    ' インスタンスの値を表示
    Console.WriteLine(mikitty)

End Sub
出力結果
藤本美貴(1985/02/26生まれ,18歳,A型)

 このように、ISerializableと適切なコンストラクタを使用すると、NonSerialized属性を使用しなくてもシリアル化の是非を決定できるのは言うまでもなく、シリアル化に際しての動作をカスタマイズすることができます。
 ちなみに、今回はSOAPシリアル化でISerializableを使用するサンプルを挙げましたが、バイナリシリアル化でも全く同じようにシリアル化できるので、シリアル化制御とシリアル化方法を場合に応じて適切に使い分けることができます。