連載

.NETで簡単XML

第15回 川俣流XMLプログラミングの定石(1)

株式会社ピーデー 川俣 晶
2004/03/06

■シリアライズ(シリアル化)

 XML文書は、シリアライズを通して読み書きすることができる。シリアライズされるXML文書の構造は、クラスの定義や、それに含まれる属性によって決定される。そのため、許される構造に合致しないXML文書を読み書きすることはできず、万能ツールではない。

 シリアライズの最大の特徴は、ほとんどプログラミングすることなく、必要な情報を読み書きするコードが手に入ることである。その点で、最も手軽なXML文書を扱う方法であるといえる。しかし、実際に筆者がプログラムを記述する際に、シリアライズの機能はあまり使わない。いくつかの問題点があるためである。

 まず、確認しておかねばならないことは、シリアライズとは本来オブジェクトの内容を一連のビット列に置き換える処理であるということだ。XML文書にシリアライズする場合は、そのビット列がXML文書の要件を満たしていることになる。このような仕組みである関係上、オブジェクトの内容と、出力されるXML文書の内容は、すべて1対1で対応することになる。

 しかし、実際にプログラムを開発しているときに、オブジェクトの内容と出力されるXML文書の内容を常に対応させ続けることが可能とは限らない。例えば、こういうケースがあったとしよう。商品の情報をXML文書として保存する際、その商品コードを保存するようにしていたとする。しかし、システムをバージョンアップした時点で商品コードが新しいコードに一新されたとしよう。新システムは、もちろん新商品コードで処理を行う。しかし、過去のシステムとの互換性の都合上、保存されるXML文書には旧商品コードで保存される必要があるとしよう。このようなシチュエーションを、DOMやXmlReader/XmlWriterのような万能ツールはうまく扱うことができる。どんな処理も、プログラムを組んで明示的に実行させているので、それを修正すれば、商品コードの変換などは朝飯前だろう。書き込む商品コードは旧商品コードに変換し、読み込む商品コードは新商品コードに変換すればよいだけのことだ。しかし、読み書きがブラック・ボックスとして処理されるシリアライズではそうはいかない。

 例えば商品コードを1つ含み、XmlSerializerクラス(System.Xml.Serialization名前空間)を使ってシリアライズ可能なクラスを記述してみよう。

Public Class Item
    Private m_code As String
    Public Property Code() As String
        Get
            Return m_code
        End Get
        Set(ByVal Value As String)
            m_code = Value
        End Set
    End Property
End Class
サンプル・プログラム3:商品コードを含む、XmlSerializerクラスでシリアライズ可能なクラス(VB.NET版C#版

 XmlSerializerクラスを使用してこれをシリアライズすると、以下のようなXML文書が得られる。シリアライズするコードは、すでに本連載で何回も解説済みなので繰り返さない。

<?xml version="1.0"?>
  <Item xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <Code>old0123456789</Code>
</Item>
サンプル・プログラム3のクラスをシリアライズして作成されるXML文書の例

 このクラスに、プログラム内部からCodeプロパティにアクセスしたときには新商品コードを返し、シリアライズする場合は旧商品コードを出力するような変更を加えることはできない。なぜなら、シリアライズの際もCodeプロパティにアクセスされることになるため、どちらもケースも同じ値を扱うしか選択の余地がないからだ。

 では、プログラム内部からCodeプロパティにアクセスしたときと、シリアライズするときで、異なる値を扱うようにする方法はないのだろうか。SoapFormatterクラス(System.Runtime.Serialization.Formatters.Soap名前空間)を使えば、それは実現できそうである。SoapFormatterクラスを使ったシリアライズでは、privateなメンバの値も直接保存されるのである。その際に、プロパティを経由したりはしない。ならば、プロパティ内部で新旧商品コードの変換を行えば、何とか実現できそうである。実際に、実現した例を以下に示す。ここでは旧商品コードは最初の3文字がold、新商品コードは最初の3文字がnewで、そのほかの文字は同じという前提で作成している。

<Serializable()> _
Public Class Item
    Private m_code As String
    Public Property Code() As String
        Get
            Return m_code.Replace("old", "new")
        End Get
        Set(ByVal Value As String)
            m_code = Value.Replace("new", "old")
        End Set
    End Property
End Class
サンプル・プログラム4:シリアライズ時に新旧の商品コードを変換するクラスの記述例(VB.NET版C#版

 SoapFormatterクラスを使ってこれをシリアライズしてから、デシリアライズ(シリアライズ解除)してCodeプロパティの値を表示すると以下のようになる。

new0123456789
SoapFormatterクラスを使用してデシリアライズした場合に表示されるCodeプロパティの値

 シリアライズされた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:Item id="ref-1" xmlns:a1=
"http://schemas.microsoft.com/clr/nsassem/
VBSample004/VBSample004%2C%20Version%3D1.0.1493.32031%2C%20
Culture%3Dneutral%2C%20PublicKeyToken%3Dnull">
<m_code id="ref-3">old0123456789</m_code>
</a1:Item>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
サンプル・プログラム4のクラスをシリアライズして作成されるXML文書の例

 これを見て分かるとおり、XML文書には「old0123456789」と旧商品コードが保存されているが、プロパティからアクセスした場合は、新商品コードが返されている。

 これでうまく行ったかのように見えるが、課題が残る。新旧商品コードの変換が、プロパティにアクセスするごとに発生している。もし、コードの変換がデータベースを使用するなど、重い処理が必要とされるのなら、プロパティに頻繁にアクセスするプログラムは極端に実行速度が遅くなってしまう可能性があり得る。しかし、本当に変換が必要なのはファイルに読み書きする場合だけで、それ以外は常に新商品コードだけあれば問題ないのだ。このような処理方法を取る限り、商品コードの変換を、読み書きを行うときだけに限定する簡単な方法はない。簡単には済まないが、ISerializableインターフェイスを実装すれば、ある程度思い通りにXML文書の内容がコントロールできるので、要求を実現することができる。しかし、そこまで手間を掛けても、完全にXML文書の内容をコントロールすることができるわけではない。それなら、DOMやXmlReader/XmlWriterを使ってしまった方が楽ではないか、と筆者は思うのである。もちろん、これは筆者の個人的な考えであって、異なる考え方の人もいると思う。また、前提が異なれば、別の結論もあり得るだろう。しかし、シリアライズを使うと、後から大きな手間が発生する可能性があることは意識しておく価値があるだろう。

 このほかに考えられる問題について簡単に触れておく。読み書きするXML文書のスキーマや仕様が変更される場合があった場合、シリアライズを使用すると、それにうまく適応できない可能性が出てくる。例えば、将来の拡張に対応するために、未知の要素を読み飛ばすといった処理を記述する場合がある。また、過去のプログラムが生成したXML文書との互換性を維持するために、異なる書式で書かれたデータを同じものと見なして受け付けることもある。こういった処理を行わせるために、シリアライズは十分な柔軟性を持っているとは言い難い。その点でも、シリアライズを使うよりは、DOMやXmlReader/XmlWriterを使う方が、いくらでも柔軟に対処するコードを書き加えることができて好ましいように思える。

 なお、念のために付け加えるなら、これらは任意のXML文書を扱う場合の話題である。あくまで、オブジェクトをシリアライズするという目的の場合は、シリアライズはまさにそのために使用できる最適な技術といえる。

 今回はXML文書の読み書きに関する筆者なりの定石について述べた。次回も引き続きXML文書のプログラミングで重要な定石を紹介する。次回で本連載は完結する。請うご期待である。End of Article


 INDEX
  .NETで簡単XML
  第15回 川俣流XMLプログラミングの定石(1)
    1.XMLプログラミングにおける定石の必要性
    2.XMLファイル書き込み時の問題点
    3.安全なXMLファイル書き込みのための定石
    4.XML文書の読み書きにどの方法を使うか
  5.シリアライズを使った読み書きの問題点
 
インデックス・ページヘ  「連載 :.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 記事ランキング

本日 月間