.NET Frameworkでの XML シリアル化

 

大胆なオバサンジョ
Microsoft Corporation

2003 年 1 月 23 日

概要:Dare Obasanjo では、XML シリアル化を使用して、W3C 標準をサポートし、相互運用性を向上させながら、.NET Framework内で厳密に型指定された XML を処理する方法について説明します。 この記事には FAQ も含まれています。 (11ページ印刷)

xml01202003_sample.exeをダウンロードします

これまでのストーリー

前の列の「 XPath を使用して XML ドキュメントにクエリを実行する場合と XMLスキーマで名前空間を操作する場合に知っておくべきことと回避すること」では、個人用ライブラリ内の書籍を追跡するための XML 形式を作成する方法について説明しました。 この形式を使用しながら、 XPathXML スキーマなど、W3C の推奨事項のさまざまな側面について説明しました。 今月の記事では、XML 形式で xml シリアル化テクノロジを.NET Frameworkで使用し、.NET Framework ベースの XML シリアル化に関してよく寄せられる質問に回答します。 一緒に来て、それは興味深い乗り物でなければなりません。

.NET Frameworkでの XML シリアル化の概要

.NET Frameworkでの XML シリアル化の主な目的は、XML ドキュメントとストリームを共通言語ランタイム オブジェクトに変換し、その逆を可能にすることです。 XML を共通言語ランタイム オブジェクトにシリアル化すると、XML ドキュメントを従来のプログラミング言語を使用して処理しやすい形式に変換できます。 一方、オブジェクトを XML にシリアル化すると、オープンで標準に準拠し、プラットフォームに依存しない方法で、このようなオブジェクトの 状態 を永続化または転送することが容易になります。

.NET Frameworkの XML シリアル化では、指定した W3C XML スキーマ定義 (XSD) スキーマに準拠する XML、または SOAP 仕様のセクション 5 で定義されているシリアル化形式に準拠する XML としてオブジェクトのシリアル化がサポートされています。 XML シリアル化中は、オブジェクトのパブリック プロパティとフィールドのみがシリアル化されます。 また、型の忠実性は、XML シリアル化中に常に保持されるとは限りません。 つまり、たとえば、Library 名前空間に存在する Book オブジェクトがある場合、同じ型のオブジェクトに逆シリアル化される保証はありません。 ただし、つまり、.NET Frameworkの XML シリアル化を使用してシリアル化されたオブジェクトは、ターゲット コンピューターに元の型が存在したり、XML が.NET Frameworkを使用して処理されたりすることなく、あるコンピューターから他方のコンピューターに配布できます。 オブジェクトの XML シリアル化は、XML や SOAP などのプラットフォームに依存しないテクノロジを使用してデータを提供または使用する場合に便利なメカニズムです。

XML シリアル化プロセスによってオブジェクトに変換された XML ドキュメントは、厳密に型指定されます。 データ型情報は、W3C XML スキーマ定義 (XSD) 言語で記述されたスキーマを介して XML ドキュメント内の要素と属性に関連付けられます。 スキーマのデータ型情報を使用すると、 XmlSerializer は XML ドキュメントを厳密に型指定されたクラスに変換できます。

.NET Frameworkでの XML シリアル化の詳細については、SDK のドキュメント トピック「XML と SOAP のシリアル化」を参照してください。

書籍インベントリ アプリケーション

前の記事では、すべての書籍を一覧表示し、個人用ライブラリで利用可能であることを説明する XML ドキュメントを作成しました。 リフレクションの際に、テキスト エディターで生の XML ファイルを編集する代わりに、ドキュメントを表示および操作するための GUI インターフェイスを使用することにしました。 このアプリケーションの作成で最初に行った手順は、 System.Windows.Forms 名前空間 のクラスを見て、ニーズをすぐに満たすことができるかどうかを確認することでした。 DataGrid クラスは有望に見えました。

DataGrid クラスの潜在的なデータ ソースの説明には、単一次元配列が含まれていました。これは、書籍の順次一覧が XML シリアル化によって配列にマップされる可能性があることを想像したため、コードを打ちました。 次に示すスキーマを C# クラスに変換して、これを試してみることにしました。

<?xml version="1.0" encoding="UTF-8" ?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" 
targetNamespace="urn:xmlns:25hoursaday-com:my-bookshelf" 
xmlns:bk="urn:xmlns:25hoursaday-com:my-bookshelf" 
elementFormDefault="qualified">
   <xs:element name="books">
      <xs:complexType>
         <xs:sequence>
            <xs:element name="book" type="bk:bookType" 
maxOccurs="unbounded" />
         </xs:sequence>
      </xs:complexType>
   </xs:element>
   <xs:complexType name="bookType">
      <xs:sequence>
         <xs:element name="title" type="xs:string" />
         <xs:element name="author" type="xs:string" />
         <xs:element name="publication-date" type="xs:date" />
      </xs:sequence>
      <xs:attribute name="publisher" type="xs:string" />
      <xs:attribute name="on-loan" type="xs:string" />
   </xs:complexType>
</xs:schema>

.NET Framework SDK には、XSD スキーマを C# クラスに変換するために使用できる XML スキーマ定義ツール xsd.exeが用意されています。 コマンド ラインで次のコマンドを発行して、スキーマ ファイルを C# ソース ファイルに変換しました。

   xsd.exe  /c books.xsd 

生成された C# クラスは、XmlSerializer がクラスを XML に変換する方法に関する情報を提供する属性で装飾されています。 XmlRootAttributeXmlElementAttributeおよび XmlAttributeAttribute 属性は、生成された XML のルート、要素、および属性ノードになるクラス、フィールド、またはプロパティを指定するために使用されます。 スキーマから生成されたクラスを次に示します。

using System.Xml.Serialization;

/// <remarks/>
[System.Xml.Serialization.XmlTypeAttribute(Namespace=
"urn:xmlns:25hoursaday-com:my-bookshelf")]
[System.Xml.Serialization.XmlRootAttribute("books", 
Namespace="urn:xmlns:25hoursaday-com:my-bookshelf", IsNullable=false)]
public class books {
    
    /// <remarks/>
    [System.Xml.Serialization.XmlElementAttribute("book")]
    public bookType[] book;
}

/// <remarks/>
[System.Xml.Serialization.XmlTypeAttribute(Namespace=
"urn:xmlns:25hoursaday-com:my-bookshelf")]
public class bookType {
    
    /// <remarks/>
    public string title;
    
    /// <remarks/>
    public string author;
    
   /// <remarks/>
   [System.Xml.Serialization.XmlElementAttribute("publication-date", 
DataType="date")]      
    public System.DateTime publicationdate;

    /// <remarks/>
    [System.Xml.Serialization.XmlAttributeAttribute()]
    public string publisher;
    
    /// <remarks/>
    [System.Xml.Serialization.XmlAttributeAttribute("on-loan")]
    public string onloan;
}

メモpublicationdate フィールドに注釈を付ける属性には、DataType プロパティがあります。 型に完全に一致する型xs:dateが.NET Frameworkにありません。 最も近い一致は、日付と時刻のデータを格納する System.DateTime です。 DataType プロパティを "date" として指定すると、XmlSerializer は DateTime オブジェクトの日付部分のみをシリアル化します。

XmlSerializer は、スキーマ内の型bookTypeの複数の book 要素を という名前bookのオブジェクトのbookType配列にマップしました。 DataGrid コントロールのデータ ソースに関するドキュメントのセクションで、配列内のオブジェクトが DataGrid にバインドされる場合は パブリック プロパティ が必要であることを指摘するまで、クラスを DataGrid にバインドする準備ができていると思いました。 XSDツールはパブリックフィールドを作成したので、フィールドをプライベートにし、代わりにプロパティを介して公開することでクラスを修正する必要がありました。 その後、XMLSerializerはパブリックフィールドとプロパティの属性のみをチェックするため、XMLシリアル化固有の属性をフィールドからプロパティに移動する必要がありました。 変更されたクラスを次に bookType 示します。

   /// <remarks/>
   [System.Xml.Serialization.XmlTypeAttribute(Namespace=
"urn:xmlns:25hoursaday-com:my-bookshelf")]
   public class bookType 
   {
      /// <remarks/>
      private string _title;
      public string title{
         get{ return _title; }
         set { _title = value; }
      }
        
      /// <remarks/>
      private string _author;
      public string author
      {
         get{ return _author; }
         set { _author = value; }
      }
        
      /// <remarks/>
      private  System.DateTime _publicationdate;
      [System.Xml.Serialization.XmlElementAttribute("publication-date",
 DataType="date")]      
      public System.DateTime publicationdate
      {
         get{ return _publicationdate; }
         set { _publicationdate = value; }
      }
        
      
      private string _publisher;
      /// <remarks/>
      [System.Xml.Serialization.XmlAttributeAttribute()]
      public string publisher
      {
      
         get{ return _publisher; }
         set { _publisher = value; }
      }
        
      private  string _onloan;
      [System.Xml.Serialization.XmlAttributeAttribute("on-loan")]      
      public string onloan
      {
         get{ return _onloan; }
         set { _onloan = value; }
      }
   }

前述の変更により、書籍のリストを XML として DataGrid にバインドできるようになりました。 Visual Studio® .NET フォーム デザイナーを起動すると、ナビゲーション目的のいくつかのボタンと共に、 DataGrid をフォームにすばやくドラッグ アンド ドロップしました。 最後の手順では、フォームが読み込まれた後に DataGrid が XML にバインドされていることを確認するためのコードを追加しました。 私の Form_Load クラスのメソッドを次に示します。

private void Form1_Load(object sender, System.EventArgs e)
     {
       try{
         TextReader reader = new StreamReader("books.xml");
         XmlSerializer serializer = new XmlSerializer(typeof(books));
         myBooks = (books)serializer.Deserialize(reader);
         reader.Close();
        
         //currency manager used for cursoring through book array in UI
         currencyManager = 
(CurrencyManager)dataGrid1.BindingContext[myBooks.book];

         dataGrid1.DataSource= myBooks.book;

       }catch(XmlException xe){
         MessageBox.Show (xe.Message, "XML Parse Error", 
                MessageBoxButtons.OK, MessageBoxIcon.Error);

       }catch(InvalidOperationException ioe){
         MessageBox.Show (ioe.InnerException.Message, "XML 
Serialization Error", 
                MessageBoxButtons.OK, MessageBoxIcon.Error);

       }
     }

そして、それが必要なすべてです。 アプリケーションで編集する予定の XML ドキュメントを次に示します。

<books xmlns="urn:xmlns:25hoursaday-com:my-bookshelf" >
  <book publisher="QUE">
    <title>XML By Example</title>
    <author>Benoit Marchal</author>
    <publication-date>1999-12-31</publication-date>
  </book>
  <book publisher="Addison Wesley" on-loan="Dmitri">
    <title>Essential C++</title>
    <author>Stanley Lippman</author>
    <publication-date>2000-10-31</publication-date>
  </book>
  <book publisher="WROX">
    <title>XSLT Programmer's Reference</title>
    <author>Michael Kay</author>
    <publication-date>2001-04-30</publication-date>
  </book>
  <book publisher="Addison Wesley" on-loan="Sanjay">
    <title>Mythical Man Month</title>
    <author>Frederick Brooks</author>
    <publication-date>1995-06-30</publication-date>
  </book>
  <book publisher="Apress">
    <title>Programmer's Introduction to C#</title>
    <author>Eric Gunnerson</author>
    <publication-date>2001-06-30</publication-date>
  </book>
</books>

次の図 1 は、Book Inventory アプリケーションの上記の XML ドキュメントにバインドされた DataGrid を示しています。

図 1. 書籍インベントリ アプリケーション

アプリケーションでは DataGrid にバインドされた XML ドキュメントの内容を編集できますが、インベントリ ファイルに新しい書籍を追加または削除できないことに注意してください。 DataGrid を使用して書籍の数を変更できないのは、配列にバインドされている場合に適用される制限です。 この問題は、他のデータ ソースにバインドされている場合には存在しません。

完全を期すために、データ バインディングの問題を解決するための別のアプローチを強調する必要があります。 ReadXml() メソッドと ReadXmlSchema() メソッドをそれぞれ使用して XML ドキュメントとスキーマを DataSet に読み込み、それを DataGrid にバインドできました。 この代替方法の使用方法の説明については、「XML スキーマからの DataSet リレーショナル構造の生成 (XSD)」セクションの .NET SDK ドキュメントを参照してください。 XML シリアル化の代わりに DataSet クラスを使用するコード例も、記事のダウンロード ファイルに含まれています。

XML シリアル化に関してよく寄せられる質問

.NET Frameworkでの XML シリアル化について、microsoft.public.dotnet.xmlニュースグループでよく寄せられる質問の一覧を次に示します。

Q: 例外やフォントなどの.NET Frameworkクラスをシリアル化できないのはなぜですか?

A: XmlSerializer は主に、XSD 準拠のデータ構造への XML データ バインディングと、特別なコード アクセス特権のない操作という 2 つの目標を念頭に置いて設計されています。 これらの 2 つの目標は、一部の種類のオブジェクトの汎用オブジェクト永続化ソリューションとして XmlSerializer に対して機能します。

汎用シリアル化では、プライベート フィールドにアクセスしたり、フレームワークの標準オブジェクト構築プロセスを渡したり、特別な特権が必要になる場合があります。 System.Runtime.Serialization.Formatters.Soap 名前空間の SoapFormatter は、これらの制限の対象ではないが、操作するには完全な信頼が必要な代替手段を提供します。 また、XML 形式も生成されます。生成は 、System.Runtime.Remoting.Metadata 名前空間の属性を使用してカスタマイズできます。

System.Runtime.Serialization.Formatters.Binary 名前空間の BinaryFormatter は、XML シリアル化がニーズを満たさない状況に対して単純なオブジェクトの永続化とトランスポートを提供するメカニズムとしても使用できます。

Q: SoapFormatter を使用しない場合、XML シリアル化用に設計されていないクラスをシリアル化するにはどうすればよいですか?

A: XmlSerializer のフィールドとプロパティを公開または非表示にする特別なラッパー クラスを設計できます。

Q: オブジェクトのコレクション操作方法シリアル化しますか?

A: XmlSerializer のコンストラクターに宣言されていない型がコレクションに含まれている場合、XmlSerializer は例外をスローします。 次のようにすることができます。

  1. 型をシリアライザーに宣言するには、コレクション内で想定される型を持つ Type[] を 渡します。

    OR

  2. Add() メソッドに一致するインデクサーを使用して、System.Collections.CollectionBase から派生した厳密に型指定されたコレクションを実装します。

Q: コレクション クラスのすべてのプロパティがシリアル化されないのはなぜですか?

A: XmlSerializer は、 IEnumerable インターフェイスまたは ICollection インターフェイスのいずれかを検出した場合にのみ、コレクション内の要素をシリアル化します。 この動作は仕様です。 唯一の回避策は、カスタム コレクションを 2 つのクラスに再ファクターすることです。そのうちの 1 つは、純粋コレクション型のいずれかを含むプロパティを公開します。

Q: ハッシュテーブルをシリアル化できないのはなぜですか?

A: XmlSerializer は 、IDictionary インターフェイスを実装するクラスを処理できません。 これは、スケジュールの制約と、XSD 型システムに対応するハッシュテーブルが存在しないという事実によるものです。 唯一の解決策は、 IDictionary インターフェイスを実装しないカスタム ハッシュテーブルを実装することです。

Q: XmlSerializer によってスローされた例外にエラーに関する詳細が含まれていないのはなぜですか?

A: すべての情報が含まれていますが、スローされた例外の InnerException プロパティに格納されます。これは通常は InvalidOperationExceptionです。 一般に、キャッチされた例外に対して 常に ToString() を 呼び出して、例外の完全な詳細を取得する必要があります。

Q: クラスへのスキーマの変換中に XmlSerializer でサポートされていない W3C XML スキーマの側面は何ですか?

A: XmlSerializer では、次の機能はサポートされていません。

  • 列挙型以外の単純型制限ファセット。
  • 名前空間ベースのワイルドカード。
  • ID 制約。
  • 置換グループ。
  • ブロックされた要素または型。

Q: インポートとインクルードで schemaLocation 属性XSD.exeサポートされないのはなぜですか?

A: W3C XML スキーマに関する推奨事項では、この属性がヒントとして記述されています。これは、代替手段を使用してスキーマを検索できるプロセッサでは無視できます。 XSD.exeは、コマンド ラインで指定されたスキーマのみを使用してスキーマ A.xsd を変換し、スキーマ B.xsd をインポートします。

xsd.exe /c A.xsd B.xsd 

また、wsdl.exe アプリケーションは、xsd.exeする姉妹アプリケーションと見なされ、Web からダウンロードできます。 これを行い、wsdl.exeを使用する場合は、インポートとインクルードの schemaLocation ヒントに従います。

Q: XML シリアル化で使用される XSD とランタイム型のマッピングと、ADO.NET または XSD 検証で使用される XSD の違いは何ですか?

A:W3C XML スキーマ型からデータセットとスキーマ検証で使用されるランタイム型へのマッピングについては、「XML スキーマ (XSD) 型と.NET Framework型の間のデータ型のサポート」トピックで説明されています。

このマッピングは、クラスのフィールドとプロパティに XML シリアル化属性が指定されている場合に使用されるマッピングとは異なります。 各 XML シリアル化属性には、そのクラスの DataType プロパティの説明で定義されているオブジェクトから W3C XML スキーマ型へのマッピングがあります。 これには、 SoapAttributeAttribute.DataType プロパティSoapElementAttribute.DataType プロパティXmlArrayItemAttribute.DataType プロパティXmlAttributeAttribute.DataType プロパティXmlElementAttribute.DataType プロパティおよび XmlRootAttribute.DataType プロパティの説明が含まれます。

主な違いは、グレゴリオ暦の日付 (や などxs:gMonthxs:gYear,) は XML シリアル化によって文字列にマップされるのに対し、検証では DateTime オブジェクトにマップされる点です。 もう 1 つの違いは、IDREFS や NMTOKENS などの DTD ベースのリスト型が XML シリアル化によって文字列にマップされ、検証によって文字列の配列にマップされる点です。 型は xs:duration 、スキーマの検証と比較して、XML シリアル化でも異なる方法でマップされます。 XML シリアル化では文字列にマップされますが、検証では TimeSpan オブジェクトにマップされます。 型は xs:integer 、サイズに上限または下限を持たない数値として指定されます。 このため、XML シリアル化も検証も 、System.Int32 型にマップされません。 代わりに、XML シリアル化では、 xs:integer が文字列にマップされ、検証によって、.NET Framework内の整数型よりもはるかに大きい Decimal 型にマップされます。

まとめ

.NET Frameworkの XML シリアル化によって提供される機能は、.NET Framework内で厳密に型指定された XML をシームレスに処理する機能を提供します。 厳密に型指定された XML ドキュメントでは、Windows および ASP.NET フォームへのデータ バインディング、豊富なコレクション フレームワーク、プログラミング言語の選択など、.NET Frameworkの利点を活用できます。 これらはすべて、XML 1.0 や XML スキーマなどの W3C 標準をサポートするように注意しながら行われます。つまり、このような厳密に型指定されたドキュメントを転送または永続化しようとすると、XML の相互運用性にコストはかからなくなります。

謝辞

XML シリアル化チームの Doug Purdy と Stefan Pharies と Christoph Schittko がこの記事のコンテンツを確認して提供してくれたことに感謝します。

Dare Obasanjo は Microsoft の WebData チームのメンバーであり、特に、.NET Framework、Microsoft XML Core Services (MSXML)、Microsoft Data Access Components (MDAC) の System.Xml 名前空間と System.Data 名前空間内のコンポーネントを開発しています。

この記事に関する質問やコメントは、GotDotNet の Extreme XML メッセージ ボード に自由に投稿できます。