連載

.NETで簡単XML

第14回 オブジェクトをXMLでシリアライズ(6)

株式会社ピーデー 川俣 晶
2004/02/18
Page1 Page2 Page3 Page4

 ではまず、インターフェイスの実装部分から見てみよう。ISerializableインターフェイスには、GetObjectDataというメソッドが1つだけ含まれており、サンプル・ソース内で、これが実装されている。その内容はというと、引数のSerializationInfoクラス(System.Runtime.Serialization名前空間)のAddValueメソッドを3回呼んでいるだけである。このメソッドは、出力する値と名前を指定する機能を持つ。ここで指定された名前と値のペアが、シリアライズされる対象となるストリームに出力されることになる。出力する「要素名」ではなく「名前」と書いている理由は、これがBinaryFormatterクラス経由でも使用される機能であるためだ。それはさておき、この例では、「Name」という名前に対して、m_nameという式の値が関連付けられているため、m_nameの値が「Name」という名前の要素の内容として出力されている。m_addressとAgeも同様である。そして、ここでAddValueメソッドを呼んでいないm_temporaryIDの値は、XML文書に出力されないことになる。以上でシリアライズは完了する。

 次は、デシリアライズについてなのだが、これはインターフェイスを実装するのではなく、ある特定の引数を持つコンストラクタを定義することで実現する。ここでは、SerializationInfoクラスとStreamingContext構造体(System.Runtime.Serialization名前空間)を引数に持つ2番目のコンストラクタがそれに当たる。これらの引数を持つコンストラクタは、デシリアライズ時に呼び出され、デシリアライズされる結果となるオブジェクトの生成に使用される。引数のSerializationInfoクラスのインスタンスを参照すれば、デシリアライズされるインスタンスがどんな値になるか分かるので、それを参照して、メンバに値を設定してやればよい。

 具体的には、引数のSerializationInfoクラスのGetStringメソッドや、GetInt32メソッドを使って値を取り出している。値を設定する場合はAddValueメソッドですべて行えたが、これは実際には引数の型が異なる多くのメソッドが同じAddValueという名前を持っていただけであった。それに対して、値を取り出す場合は、引数の型によって名前の異なるメソッドを使用することになる。GetStringメソッドは文字列型を、GetInt32メソッドは整数型(System.Int32構造体に相当。つまりVB.NETではInteger、C#ではintに対応する)を扱う。

 GetObjectDataメソッドと、このコンストラクタがあれば、実際にフィールドに記憶されている情報とは異なった形式でのシリアライズも可能になることが想像できるだろう。例えば、本来は1つの文字列だが、処理の都合上、複数の文字列に分解して管理しているクラスを想像していただきたい。ファイル名を処理するクラスなどで、ディレクトリ名とファイル名を分離して管理する必要があるかもしれない。そのようなクラスがシリアライズされる場合、GetObjectDataメソッドでは複数の文字列を合成して1つの文字列とし、コンストラクタでは1つの文字列を分解して複数の文字列に変換する、という機能を実装することができる。

 さて、ここで1つ注意していただきたいことがある。このコンストラクタの先頭には、Protectedという指定が付いていることである。このキーワードは、このクラスを継承したクラスからしか呼び出せないことを示している。そして、このサンプルのソース・コードには、このクラスを継承したクラスは存在しない。それでもこのソース・コードは実行可能である。それどころか、コンストラクタはPrivateと指定しても動作する。コンストラクタにPrivateと指定することは、共有メソッド(静的メソッド)などの活用と合わせて、通常ならシングルトン(Singleton:複数のインスタンスの作成を禁止するパターン)を実現するためなどに使われるものである。しかし、ここでは.NET Frameworkのリフレクション機能を通してアクセスしているようで、このコンストラクタは確かに呼び出され、動作している。そして、Protectedという指定によって、このデシリアライズ専用のコンストラクタは通常のコーディング時に、ほかクラスから呼び出すことができなくなっており、誤用される危険が少なくなっている。

 なお、ここでコンストラクタにPrivateではなくProtectedを指定している理由は、次の項目を読むと理解できるだろう。継承が行われた際に、サブクラスから呼び出される可能性があるためである。

 最後にもう1つだけ補足しておこう。このサンプルのソース・コードには、もう1つ、引数のないコンストラクタが追加されている。これがないと、永遠に普通の方法でインスタンスを作成できないクラスが生まれてしまうことになる。Protected指定されたデシリアライズ用のコンストラクタは外部から普通の方法でインスタンスを作成するためには使えないのである。

ISerializableインターフェイスを使ったクラスを継承する場合の注意

 ISerializableインターフェイスを使ったクラスを継承する場合は注意が必要である。XmlSerializerクラスでシリアライズする場合も、継承したクラスを使うときに属性を付加するなどの対処が必要なケースがあったが、それとはまったく異なる注意である。

 ISerializableインターフェイスを使うということは、シリアライズされる内容の決定をすべてプログラマ自身がコントロールするということである。もし、継承されたクラスをシリアライズ可能とする場合は、スーパー・クラスのメンバも適切にシリアライズされるように正しく配慮しなければならない。

 以下は、上の例のクラスを2つに分けて、継承関係を持たせた例である。

<Serializable()> _
Public Class SimplePerson
    Implements ISerializable

    Private m_name As String = "未設定"
    Private m_address As String = "未設定"

    Public Overridable Sub GetObjectData(ByVal info As SerializationInfo, ByVal context As StreamingContext) Implements ISerializable.GetObjectData
        info.AddValue("Name", m_name)
        info.AddValue("Address", m_address)
    End Sub

    Public Sub New()
    End Sub

    Protected Sub New(ByVal info As SerializationInfo, ByVal context As StreamingContext)
        m_name = info.GetString("Name")
        m_address = info.GetString("Address")
    End Sub

    Public Property Name() As String
        Get
            Return m_name
        End Get
        Set(ByVal Value As String)
            m_name = Value
        End Set
    End Property

    Public ReadOnly Property Address() As String
        Get
            Return m_address
        End Get
    End Property

End Class

<Serializable()> _
Public Class Person
    Inherits SimplePerson

    Public Overrides Sub GetObjectData(ByVal info As SerializationInfo, ByVal context As StreamingContext)
        MyBase.GetObjectData(info, context)
        info.AddValue("Age", Age)
    End Sub

    Public Sub New()
    End Sub

    Protected Sub New(ByVal info As SerializationInfo, ByVal context As StreamingContext)
        MyBase.New(info, context)
        Age = info.GetInt32("Age")
    End Sub

    Public Age As Integer = 0
    Private _temporaryID As String = "未設定"
    Public Sub SetTemporaryID(ByVal temporaryID As String)
        _temporaryID = temporaryID
    End Sub
    Public Sub Dump()
        System.Diagnostics.Trace.WriteLine(Name)
        System.Diagnostics.Trace.WriteLine(Address)
        System.Diagnostics.Trace.WriteLine(Age)
        System.Diagnostics.Trace.WriteLine(_temporaryID)
    End Sub

End Class
サンプル・プログラム2:ISerializableインターフェイスを実装しているSimplePersonクラスを継承したPersonクラス(VB.NET版C#版

 これを実行した結果は基本的に先ほどのサンプル・プログラム1の結果と同じなので省略する。

 さて、ここで注意していただきたいことは、PersonクラスのGetObjectDataメソッド内で、MyBase.GetObjectData(info, context)(C#の場合はbase.GetObjectData(info, context);)として、スーパー・クラスであるSimplePersonクラスの同じメソッドを呼び出していることである。これによって、スーパー・クラスのメンバも正しくシリアライズされる。

 もう1つ、コンストラクタにも、スーパー・クラスのコンストラクタの呼び出しが追加されていることに注意していただきたい(MyBase.GetObjectData(info, context)、C#ではbase(info,context)の部分に当たる)。もしこれを忘れると、引数のないコンストラクタが呼ばれ、スーパー・クラスのデシリアライズが行われないという問題が起きてしまう。


 INDEX
  .NETで簡単XML
  第14回 オブジェクトをXMLでシリアライズ(6)
    1.ISerializableインターフェイスを用いたシリアライズ制御
  2.ISerializableインターフェイスを使ったクラスの継承
    3.XML Webサービス経由で使われるシリアライズの種類
    4.XmlSerializerを使ってSOAP形式でシリアライズする方法
 
インデックス・ページヘ  「連載 :.NETで簡単XML」


Insider.NET フォーラム 新着記事
  • 第2回 簡潔なコーディングのために (2017/7/26)
     ラムダ式で記述できるメンバの増加、throw式、out変数、タプルなど、C# 7には以前よりもコードを簡潔に記述できるような機能が導入されている
  • 第1回 Visual Studio Codeデバッグの基礎知識 (2017/7/21)
     Node.jsプログラムをデバッグしながら、Visual Studio Codeに統合されているデバッグ機能の基本の「キ」をマスターしよう
  • 第1回 明瞭なコーディングのために (2017/7/19)
     C# 7で追加された新機能の中から、「数値リテラル構文の改善」と「ローカル関数」を紹介する。これらは分かりやすいコードを記述するのに使える
  • Presentation Translator (2017/7/18)
     Presentation TranslatorはPowerPoint用のアドイン。プレゼンテーション時の字幕の付加や、多言語での質疑応答、スライドの翻訳を行える
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

注目のテーマ

Insider.NET 記事ランキング

本日 月間