シリアル化と逆シリアル化
Windows Communication Foundation (WCF) には新しいシリアル化エンジン、DataContractSerializer が含まれます。DataContractSerializer は、.NET Framework オブジェクトと XML を双方向で変換します。ここでは、シリアライザのしくみについて説明します。
.NET Framework オブジェクトをシリアル化するときに、シリアライザは新しいデータ コントラクト モデルも含めて、さまざまなシリアル化プログラミング モデルを認識します**。サポートされるすべての型の一覧については、「データ コントラクト シリアライザでサポートされる型」を参照してください。データ コントラクトの概要については、「データ コントラクトの使用」を参照してください。
XML を逆シリアル化するときに、シリアライザは XmlReader クラスと XmlWriter クラスを使用します。また、WCF バイナリ XML 形式を使用する場合などに、最適化された XML を生成できるように、XmlDictionaryReader クラスと XmlDictionaryWriter クラスもサポートしています。
WCF には、コンパニオン シリアライザである NetDataContractSerializer も含まれます。NetDataContractSerializer は、シリアル化されたデータの一部として .NET Framework 型名を出力するため、BinaryFormatter シリアライザや SoapFormatter シリアライザに似ています。このシリアライザは、シリアル化と逆シリアル化の終了時に、同じ型を共有する場合に使用します。DataContractSerializer と NetDataContractSerializer は、共通の基本クラスである XmlObjectSerializer から派生します。
DataContractSerializer インスタンスの作成
DataContractSerializer のインスタンスの作成は重要な手順です。インスタンスの作成後に、設定を変更することはできません。
ルート型の指定
ルート型は、シリアル化または逆シリアル化するインスタンスの型です**。DataContractSerializer には、多数のコンストラクタ オーバーロードがありますが、type パラメータを使用して、少なくともルート型を指定する必要があります。
特定のルート型に対応するシリアライザを作成した場合、このシリアライザを使用して別の型をシリアル化 (または逆シリアル化) することはできません。ただし、対象の型がルート型の派生型である場合を除きます。2 つのクラスを次の例に示します。
Person
クラスのインスタンスをシリアル化または逆シリアル化する場合にのみ使用できる DataContractSerializer のインスタンスを作成するコードを次に示します。
既知の型の指定
KnownTypeAttribute 属性またはその他の機構を使用してまだ処理されていないシリアル化対象の型にポリモーフィズムが必要な場合、knownTypes パラメータを使用して、存在し得る既知の型のリストをシリアライザのコンストラクタに渡す必要があります。既知の型詳細については、 、「既知のデータ コントラクト型」を参照してください。
LibraryItem
型のコレクションを含む LibraryPatron
クラスの例を次に示します。2 番目のクラスでは、LibraryItem
型を定義しています。3 番目と 4 番目のクラス (Book
と Newspaper
) は、LibraryItem
クラスを継承しています。
knownTypes パラメータを使用して、シリアライザのインスタンスを作成するコードを次に示します。
既定のルート名と名前空間の指定
通常、オブジェクトをシリアル化すると、データ コントラクト名と名前空間に従って、最も外側にある XML 要素の既定の名前と名前空間が決定されます。内側のすべての要素の名前はデータ メンバ名から決定され、名前空間にはデータ コントラクトの名前空間が使用されます。DataContractAttribute クラスと DataMemberAttribute クラスのコンストラクタで、Name
および Namespace
の値を設定する例を次に示します。
Person
クラスのインスタンスをシリアル化すると、次のような XML が生成されます。
<PersonContract xmlns="http://schemas.contoso.com">
<AddressMember>
<StreetMember>123 Main Street</StreetMember>
</AddressMember>
</PersonContract>
ただし、rootName パラメータと rootNamespace パラメータの値を DataContractSerializer のコンストラクタに渡すことにより、ルート要素の既定の名前と名前空間をカスタマイズできます。rootNamespace は、データ メンバに対応する格納されている要素の名前空間には影響を及ぼしません。このパラメータの影響を受けるのは、最も外側の要素の名前空間だけです。
これらの値を文字列または XmlDictionaryString クラスのインスタンスとして渡すと、バイナリ XML 形式を使用して最適化できます。
オブジェクトの最大クォータの設定
DataContractSerializer の一部のコンストラクタ オーバーロードには、maxItemsInObjectGraph パラメータが含まれています。このパラメータにより、ReadObject メソッドの 1 回の呼び出しで、シリアライザがシリアル化または逆シリアル化するオブジェクトの最大数が決まります (このメソッドは常に、1 つのルート オブジェクトを読み取りますが、このオブジェクトはそのデータ メンバ内に他のオブジェクトを保持する場合があります。さらに、そうしたオブジェクトも他のオブジェクトを持つ場合があります。以降のオブジェクトについても同様です)。既定値は 65536 です。配列のシリアル化または逆シリアル化を行う場合、すべての配列エントリが個別のオブジェクトとしてカウントされることに注意してください。また、一部のオブジェクトが大きなメモリ内表現を取る場合があります。したがって、このクォータだけでは、サービス拒否攻撃の防止には不十分である可能性があります。詳細な情報については、次のページを参照してください。 「セキュリティに関するデータの考慮事項」を参照してください。このクォータはデータの読み取り時と書き込み時の両方に適用されるため、このクォータに既定値を上回る値を設定する必要がある場合は、送信側 (シリアル化) と受信側 (逆シリアル化) の両方で設定することが重要です。
ラウンド トリップ
ラウンド トリップは、1 つの操作でオブジェクトの逆シリアル化と再シリアル化が行われるときに発生します**。したがって、XML からオブジェクト インスタンスを経由し、再び XML ストリームに戻るラウンド トリップが発生します。
DataContractSerializer の一部のコンストラクタ オーバーロードには、ignoreExtensionDataObject パラメータが含まれており、既定で false に設定されています。この既定のモードでは、データ コントラクトが IExtensibleDataObject インターフェイスを実装していれば、データ コントラクトの新しいバージョンから以前のバージョンを経由し、新しいバージョンに戻るラウンド トリップで、データを失うことなく送信できます。たとえば、Person
データ コントラクトのバージョン 1 に、Name
および PhoneNumber
の各データ メンバが含まれており、バージョン 2 で Nickname
メンバを追加したとします。IExtensibleDataObject を実装している場合、バージョン 2 からバージョン 1 に情報を送信すると、Nickname
データが格納され、データを再度シリアル化したときに再出力されます。したがって、ラウンド トリップでデータが失われることはありません。詳細な情報については、次のページを参照してください。 「上位互換性のあるデータ コントラクト」および「データ コントラクトのバージョン管理」を参照してください。
ラウンド トリップでのセキュリティとスキーマ検証の問題
ラウンド トリップは、セキュリティに影響する場合があります。たとえば、大量の無関係のデータを逆シリアル化し格納した場合、セキュリティ リスクが生じる可能性があります。特に、デジタル署名を伴う場合は、検証方法のないこのようなデータの再出力について、セキュリティの問題が発生することがあります。たとえば、前のシナリオでは、バージョン 1 エンドポイントが、悪質なデータを含む Nickname
値に署名する可能性があります。また、スキーマ検証の問題が発生することもあります。エンドポイントでは、余分な値を出力せずに、記述されたコントラクトに厳密に従ったデータを常に出力することが必要な場合があります。前の例では、バージョン 1 エンドポイントのコントラクトで Name
と PhoneNumber
だけを出力するよう指定されているときに、スキーマ検証を使用すると、余分な Nickname
値の出力によって検証が失敗します。
ラウンド トリップの有効化と無効化
ラウンド トリップを無効にする場合は、IExtensibleDataObject インターフェイスを実装しないでください。型を制御できない場合は、ignoreExtensionDataObject パラメータを true に設定することで、同じ効果を得ることができます。
オブジェクト グラフの保存
次のコードに示すように、通常、シリアライザはオブジェクト ID に留意することはありません。
発注書を作成するコードを次に示します。
billTo
フィールドと shipTo
フィールドが、同じオブジェクト インスタンスに設定されていることに注意してください。ただし、生成される XML は重複する情報を繰り返します。XML は次のようになります。
<PurchaseOrder>
<billTo><street>123 Main St.</street></billTo>
<shipTo><street>123 Main St.</street></shipTo>
</PurchaseOrder>
この方法には以下の特性があり、望ましくないことがあります。
- パフォーマンス。データのレプリケートは非効率的です。
- 循環参照。他のオブジェクトを介してであっても、オブジェクトがそれ自体を参照している場合、レプリケーションによるシリアル化は無限ループを発生させます (この状況が発生した場合、シリアライザは SerializationException をスローします)。
- セマンティクス。2 つの参照の参照先が 1 つのオブジェクトであり、2 つの同一のオブジェクトではない状態を維持することが重要な場合があります。
上記の理由から、DataContractSerializer の一部のコンストラクタ オーバーロードには、preserveObjectReferences パラメータが含まれています (既定値は false です)。このパラメータを true に設定すると、オブジェクト参照をエンコードする特殊なメソッドが使用されます。これは、WCF だけが認識するメソッドです。true に設定すると、XML コードの例は次のようになります。
<PurchaseOrder ser:id="1">
<billTo ser:id="2"><street ser:id="3">123 Main St.</street></billTo>
<shipTo ser:ref="2"/>
</PurchaseOrder>
"ser" 名前空間は、シリアル化標準名前空間である https://schemas.microsoft.com/2003/10/Serialization/ を指します。データの各部分は 1 回だけシリアル化され、ID 番号が与えられます。以降は、既にシリアル化されたデータへの参照を使用することになります。
![]() |
---|
"id" および "ref" 属性の両方がデータ コントラクト XMLElement に存在する場合、"ref" 属性が使用され、"id" 属性は無視されます。 |
このモードの以下の制限を理解しておくことが重要です。
- preserveObjectReferences が true に設定された DataContractSerializer によって生成した XML は、他のテクノロジと相互運用できません。この XML にアクセスできるのは、preserveObjectReferences が true に設定された別の DataContractSerializer インスタンスだけです。
- この機能では、メタデータ (スキーマ) はサポートされていません。生成されるスキーマは、preserveObjectReferences が false に設定されている場合にのみ有効です。
- この機能により、シリアル化および逆シリアル化プロセスの実行速度が低下することがあります。データをレプリケートする必要はありませんが、このモードではオブジェクトの比較を追加で実行する必要があります。
![]() |
---|
preserveObjectReferences モードを有効にする場合、maxItemsInObjectGraph の値を適切なクォータに設定することが特に重要となります。このモードでの配列の処理方法に起因して、攻撃者は maxItemsInObjectGraph のクォータによってのみ制限される、メモリを大量に消費させる小さい悪質なメッセージを容易に作成できます。 |
データ コントラクト サロゲートの指定
DataContractSerializer の一部のコンストラクタ オーバーロードには、dataContractSurrogate パラメータが含まれています。このパラメータは、null に設定できます。それ以外の場合は、このパラメータを使用して、データ コントラクト サロゲートを指定できます。データ コントラクト サロゲートは、IDataContractSurrogate インターフェイスを実装する型です**。このインターフェイスを使用して、シリアル化および逆シリアル化プロセスをカスタマイズできます。詳細な情報については、次のページを参照してください。 「データ コントラクト サロゲート」を参照してください。
シリアル化
次の情報は XmlObjectSerializer を継承するすべてのクラス (DataContractSerializer クラスおよび NetDataContractSerializer クラスを含む) に適用されます。
単純なシリアル化
オブジェクトをシリアル化する最も簡単な方法は、オブジェクトを WriteObject メソッドに渡すことです。このメソッドには 3 つのオーバーロードがあり、それぞれ Stream、XmlWriter、および XmlDictionaryWriter への書き込みに対応しています。Stream オーバーロードの場合、出力は UTF-8 エンコードの XML です。XmlDictionaryWriter オーバーロードを使用すると、シリアライザによってバイナリ XML の出力が最適化されます。
WriteObject メソッドを使用した場合、シリアライザはラッパー要素の既定の名前と名前空間を使用し、コンテンツと共に書き込みます (前の「既定のルート名と名前空間の指定」セクションを参照してください)。
XmlDictionaryWriter を使用した書き込みの例を次に示します。
これにより作成される XML は、次のようになります。
<Person>
<Name>Jay Hamlin</Name>
<Address>123 Main St.</Address>
</Person>
段階的なシリアル化
終了要素を書き込み、オブジェクトの内容を書き込み、ラッパー要素を閉じるには、それぞれ WriteStartObject、WriteObjectContent、WriteEndObject の各メソッドを使用します。
![]() |
---|
これらのメソッドの Stream オーバーロードはありません。 |
この段階的なシリアル化には、2 つの一般的な使用方法があります。次の例に示すように、1 つは属性やコメントなどのコンテンツを WriteStartObject と WriteObjectContent の間に挿入する場合に使用します。
これにより作成される XML は、次のようになります。
<Person serializedBy="myCode">
<Name>Jay Hamlin</Name>
<Address>123 Main St.</Address>
</Person>
もう 1 つは、次のコードに示すように、WriteStartObject と WriteEndObject を使用せずに、独自のカスタム ラッパー要素を書き込む場合 (ラッパーの書き込みを省略する場合もあります) に使用します。
これにより作成される XML は、次のようになります。
<MyCustomWrapper>
<Name>Jay Hamlin</Name>
<Address>123 Main St.</Address>
</MyCustomWrapper>
![]() |
---|
段階的なシリアル化を使用すると、スキーマが無効な XML が生成されることがあります。 |
逆シリアル化
次の情報は XmlObjectSerializer から派生するすべてのクラス (DataContractSerializer および NetDataContractSerializer クラスを含む) に適用されます。
オブジェクトを逆シリアル化する最も簡単な方法は、ReadObject メソッド オーバーロードのいずれかを呼び出すことです。3 つのオーバーロードがあり、それぞれ XmlDictionaryReader、XmlReader、および Stream を使用した読み取りに対応しています。Stream オーバーロードは、クォータによって保護されていないテキスト形式の XmlDictionaryReader を作成するため、信頼されたデータを読み取る場合にのみ使用します。
ReadObject メソッドが返すオブジェクトは、適切な型にキャストする必要があります。
DataContractSerializer のインスタンスと XmlDictionaryReader を作成し、Person
インスタンスを逆シリアル化するコードを次に示します。
ReadObject メソッドを呼び出す前に、ラッパー要素またはラッパー要素の前にあるコンテンツ ノード以外のノードに XML リーダーを配置します。これを行うには、次のコードに示すように、XmlReader またはその派生クラスの Read メソッドを呼び出し、NodeType を調べます。
ReadObject
にリーダーを渡す前に、このラッパー要素の属性を読み取ることができます。
簡単な ReadObject
オーバーロードのいずれかを使用すると、デシリアライザはラッパー要素の既定の名前と名前空間を検索し (前の「既定のルート名と名前空間の指定」セクションを参照)、不明な要素が見つかった場合は例外をスローします。前の例では、<Person>
ラッパー要素が必要とされます。予想どおりの名前が付けられた要素にリーダーが配置されているかどうかを確認するために、IsStartObject メソッドが呼び出されます。
ラッパー要素のこの名前チェックを無効にする方法があります。ReadObject メソッドの一部のオーバーロードは、ブール型パラメータ verifyObjectName を取得します。このパラメータは、既定で true に設定されています。このパラメータを false に設定すると、ラッパー要素の名前と名前空間が無視されます。これは、前述の段階的なシリアル化機構を使用して書き込まれた XML を読み取る際に役立ちます。
NetDataContractSerializer の使用
DataContractSerializer と NetDataContractSerializer の大きな違いは、DataContractSerializer は、データ コントラクト名を使用するのに対し、NetDataContractSerializer は、シリアル化された XML に .NET Framework アセンブリと型の完全名を出力するという点です。これは、シリアル化エンドポイントと逆シリアル化エンドポイント間で、まったく同じ型を共有する必要があることを意味します。逆シリアル化する正確な型が常にわかっているため、NetDataContractSerializer では既知の型機構は必要ないということです。
ただし、次のようないくつかの問題が発生する可能性があります。
- セキュリティ。逆シリアル化する XML で見つかったすべての型が読み込まれます。これを利用して、悪質な型が強制的に読み込まれるおそれがあります。信頼できないデータでの NetDataContractSerializer の使用は、(Binder プロパティまたはコンストラクタ パラメータを使用して) シリアル化バインダを使用する場合に限定する必要があります。バインダが読み込みを許可するのは安全な型だけです。このバインダ機構は、System.Runtime.Serialization 名前空間の型が使用するものと同じです。
- バージョン管理。XML で型とアセンブリの完全名を使用する場合、型をバージョン管理する方法が厳しく制限されます。型名、名前空間、アセンブリ名、およびアセンブリのバージョンを変更することはできません。AssemblyFormat プロパティまたはコンストラクタ パラメータを既定値の Full ではなく、Simple に設定すると、アセンブリのバージョンを変更できるようになりますが、ジェネリック パラメータの型を変更することはできません。
- 相互運用性。.NET Framework 型名とアセンブリ名は XML に含まれるため、.NET Framework 以外のプラットフォームでは、生成されたデータにアクセスできません。
- パフォーマンス。型名とアセンブリ名を書き込むと、生成される XML のサイズが大幅に増加します。
この機構は、.NET Framework リモート処理で使用されるバイナリ シリアル化または SOAP シリアル化 (具体的には、BinaryFormatter と SoapFormatter) に似ています。
NetDataContractSerializer の使用方法と DataContractSerializer の使用方法は似ていますが、次のような違いがあります。
- コンストラクタでルート型を指定する必要はありません。NetDataContractSerializer の同じインスタンスを使用して、すべての型をシリアル化できます。
- コンストラクタは、既知の型のリストを受け入れません。型名を XML にシリアル化する場合、既知の型機構は不要です。
- コンストラクタは、データ コントラクト サロゲートを受け入れません。代わりに、(SurrogateSelector プロパティに割り当てられた) surrogateSelector という ISurrogateSelector のパラメータを受け入れます。これは、従来のサロゲート機構です。
- コンストラクタは、AssemblyFormat プロパティに割り当てられた、FormatterAssemblyStyle の assemblyFormat というパラメータを受け入れます。前述のように、このパラメータを使用することで、シリアライザのバージョン管理機能を強化できます。これは、バイナリ シリアル化または SOAP シリアル化の FormatterAssemblyStyle 機構と同じです。
- コンストラクタは、Context プロパティに割り当てられた context という StreamingContext のパラメータを受け入れます。このパラメータを使用して、シリアル化する型に情報を渡すことができます。この使用方法は、他の System.Runtime.Serialization クラスで使用する StreamingContext 機構と同じです。
- Serialize メソッドと Deserialize メソッドは、WriteObject メソッドと ReadObject メソッドのエイリアスです。これらのメソッドは、より一貫性のあるプログラミング モデルで、バイナリ シリアル化または SOAP シリアル化を使用できるようにするために存在しています。
このような機能詳細については、 、「Binary Serialization」を参照してください。
通常、NetDataContractSerializer と DataContractSerializer が使用する XML 形式には互換性がありません。したがって、これらのシリアライザの一方を使用してシリアル化し、もう一方を使用して逆シリアル化するシナリオはサポートされていません。
また、NetDataContractSerializer は、オブジェクト グラフの各ノードについて、.NET Framework 型とアセンブリの完全名を出力しません。この情報を出力するのは、情報が不明確な場合だけです。つまり、ポリモーフィックである場合に、ルート オブジェクト レベルで出力します。
関連項目
リファレンス
DataContractSerializer
NetDataContractSerializer
XmlObjectSerializer