連載

.NETで簡単XML

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

株式会社ピーデー 川俣 晶
2004/01/21
Page1 Page2 Page3

 これらの相違はなぜ起きているのだろうか。そして、それは何を意味しているのだろうか。

 まず出力されたXML文書の内容の相違について見てみよう。「SOAP」という言葉を知っている読者なら、SoapFormatterというクラス名や、http://schemas.xmlsoap.org/soap/encoding/などの名前空間URIからうすうす察しが付くかもしれない。これは、XML Webサービスなどで使われるプロトコルであるSOAP(Simple Object Access Protocol)形式でシリアライズされた結果である。つまり、SoapFormatterクラスは、SOAPという仕様が存在することを前提にして、XmlSerializerクラスを使ったサンプル・ソースとは異なる目的と意図を持ったところで使われるべきものといえる。SOAPを前提としたデータのやりとりを行う場合は、XmlSerializerクラスではなく、SoapFormatterクラスを使用した方が便利という状況があるかもしれない。

 さて、XmlSerializerクラスと、SoapFormatterクラスの相違を、SOAP形式にするか否かという相違だと理解すると、出力結果の4行目の相違が理解できない。実は、両者の間には、単純にSOAP形式か否かというような単純な相違ではなく、もっと本質的な相違が存在している。

 すでに説明したように、XmlSerializerクラスによって行うシリアライズは、パブリック・フィールドとパブリック・プロパティのみを扱う。それに対して、SoapFormatterクラスは、(何も指定しないデフォルト状態で使うと)すべてのフィールドがシリアライズの対象となる。この条件には、パブリックであるか否かという条件はなく、また、プロパティは対象に含まれていない。つまり、この場合、4行の出力結果のうち3行は同じ内容の結果となっていたが、同じ結果の行も同じ動作の結果ではないケースがあるということである。XmlSerializerクラスを使った場合にプロパティ経由でシリアライズされていたm_nameの値は、SoapFormatterクラスではフィールド経由でシリアライズされていたわけである。また、初期値として設定した後、書き換える手段が用意されていないm_temporaryIDの値も、実はシリアライズ、デシリアライズを通じて保存されていたのである。

 それらの証拠は、生成されたXML文書に現れている。要素名は、すべてm_nameのようなフィールド名になっていて、Nameのようなプロパティ名は現れていない。また、XmlSerializerクラスを使った場合には対象になっていなかったm_addressやm_temporaryIDも確かにXML文書に書き込まれていることが分かるだろう。

 さて、すべてのフィールドが自動的に何もかもシリアライズされるとなると、困る状況もあり得るだろう。例えば、一時的に取得したハンドル番号など、永続的に保存しても何の意味もなく、かえってトラブルの元になるだけ、という情報もあり得る。それに対する抑止力的な意味を持つ属性が存在する。それが、Personクラスに付加されているSerializable属性(System名前空間)である。SoapFormatterクラスは、このSerializable属性が付加されたクラスのみをシリアライズ対象とする。つまり、SoapFormatterクラスでシリアライズしてもよいクラスにのみこの属性を付けることで、シリアライズされては困る状況に対処することができる。

 もし、Serializable属性を取り去ってこのサンプル・プログラムを実行すると、以下のような例外が発生することになる。

'System.Runtime.Serialization.SerializationException' のハンドルされていない例外が mscorlib.dll で発生しました。
追加情報 : アセンブリ CSSample016, Version=1.0.1434.28315, Culture=neutral, PublicKeyToken=null の型 Person は シリアル化可能として設定されていません。
Serializable属性がない場合に発生する例外

 最後に、両者の間の小さな相違を指摘しておこう。XmlSerializerクラスを使った場合には、XmlSerializerクラスのコンストラクタにシリアライズする型の情報をGetType(Person)(C#ではtypeof(Person))として指定していた。しかし、SoapFormatterクラスを使った場合には、そのような引数を渡していない。これは、XmlSerializerクラスのインスタンスは、特定の型をシリアライズする専用品として生成されるのに対して、SoapFormatterクラスのインスタンスは扱う型が限定されない汎用品として生成されることを意味する。この相違は、異なる型を持つ複数のオブジェクトをまとめてシリアライズする場合に、コーディング上の差となって現れることになる。

シリアライズさせないフィールド

 すでに述べたとおり、すべてのフィールドが無条件でシリアライズされると困る場合がある。このような場合は、Serializable属性を付けないことにより、SoapFormatterクラスによりシリアライズの対象から外すことができることはすでに述べた。

 しかし、1つのフィールドがシリアライズされると困るからといって、クラスそのものがシリアライズ不可能になってしまっても、困る場合があるだろう。そのような状況に対処するために、XmlSerializerクラスを使う場合には、特定の項目をシリアライズ対象から外すXmlIgnore属性(System.Xml.Serialization名前空間)を使用する方法を第9回では解説した。

 SoapFormatterクラスを使用する際にも、同じように特定のフィールドをシリアライズ対象から外すNonSerialized属性(System名前空間)を使用することができる。以下はそれを使用した例である。シリアライズされるクラス以外は、前の例と同じなので、シリアライズされるクラスのソース・コードのみ掲載する。

<Serializable()> _
Public Class Person
  Private m_name As String = "未設定"
  Private m_address As String = "未設定"
  Public Age As Integer = 0
  <NonSerialized()> _
  Private m_temporaryID As String = "未設定"
  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
  Public Sub SetTemporaryID(ByVal temporaryID As String)
    m_temporaryID = temporaryID
  End Sub
  Public Sub Dump()
    System.Diagnostics.Trace.WriteLine(m_name)
    System.Diagnostics.Trace.WriteLine(m_address)
    System.Diagnostics.Trace.WriteLine(Age)
    System.Diagnostics.Trace.WriteLine(m_temporaryID)
  End Sub
End Class
サンプル・プログラム2:NonSerialized属性を使用した場合のPersonクラス(VB.NET版C#版

 これを実行すると、以下のような出力が得られる。

山田太郎
未設定
17

サンプル・プログラム2の出力結果

 また、同時に以下のような内容のXMLファイルが「c:\sample.xml」に生成される。

<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:Person id="ref-1"
xmlns:a1="http://schemas.microsoft.com/clr/nsassem/
VBSample017/VBSample017%2C%20Version%3D1.0.1437.18517%2C%20
Culture%3Dneutral%2C%20PublicKeyToken%3Dnull">
<m_name id="ref-3">山田太郎</m_name>
<m_address id="ref-4">未設定</m_address>
<Age>17</Age>
</a1:Person>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
NonSerialized属性を使用した場合に生成されるXML文書

 さて、出力されたXML文書を見ると、確かに、NonSerialized属性を付けたメンバ、m_temporaryIDが出力されていないことが分かるだろう。この属性を使うことで、シリアライズされて困るフィールドの出力を抑止することができる。効能は、XmlIgnore属性と同様だが、XmlSerializerクラスとSoapFormatterクラスは相互に関連のない独立したクラスであるため、別のNonSerialized属性を使う必要がある。

 余談だが、出力結果の4行目が空行になっていることに注意しよう。クラスが初期化した値はデシリアライズ時にNothing(C#ではnull)で上書きされているわけである。シリアライズの対象にならないことと、デシリアライズに書き換えられないことはイコールではない。これはNonSerialized属性を使った場合とは異なる動作である。


 INDEX
  .NETで簡単XML
  第13回 オブジェクトをXMLでシリアライズ(5)
    1.SoapFormatterを使ったシリアライズ
  2.シリアライズさせないフィールド
    3.SoapFormatterの姉妹クラス、BinaryFormatterクラス
 
インデックス・ページヘ  「連載 :.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 記事ランキング

本日 月間