シリアル化のカスタマイズ
シリアル化のカスタマイズは、型のシリアル化と逆シリアル化を制御するプロセスです。シリアル化を制御することで、シリアル化の互換性を保証できます。つまり、型の基本機能を壊すことなく、1 つの型の複数のバージョンの間でシリアル化および逆シリアル化を行えます。たとえば、最初のバージョンにおいてフィールドを 2 つだけ保持する型があるとします。新しいバージョンでは、これにいくつかのフィールドが追加されています。この場合、2 番目のバージョンのアプリケーションでは、両方の型をシリアル化および逆シリアル化できる必要があります。シリアル化の制御方法については、以下の各セクションを参照してください。
シリアル化の実行中および実行後のカスタム メソッドの実行
ベスト プラクティス (.Net Framework バージョン 2.0 で導入された方法) は、シリアル化の実行中および実行後にデータを収集する各メソッドに、以下の属性を適用する方法です。
これらの属性を適用すると、シリアル化および逆シリアル化プロセスの 4 つのフェーズのうち、いずれか、またはすべてに型を関与させることができます。これらの属性は、各フェーズで実行する必要のある型のメソッドを指定します。これらのメソッドはシリアル化ストリームにはアクセスしませんが、これらを使用すると、シリアル化の実行前および実行後、あるいは逆シリアル化の実行前および実行後にオブジェクトを変更できます。これらの属性は、型の継承階層の全レベルで適用でき、各メソッドは基本クラスから最派生クラスまで、階層内で呼び出されます。このしくみにより、シリアル化および逆シリアル化の責任を最派生実装にゆだねることができるので、ISerializable インターフェイスの実装の複雑性、およびその実装の結果発生する問題を回避できます。また、フォーマッタはフィールド値の設定、およびシリアル化ストリームからの取得を無視できます。シリアル化および逆シリアル化の詳細と例については、上記のいずれかのリンクをクリックしてください。
また、シリアル化が可能な既存の型に新規フィールドを追加する場合は、このフィールドに OptionalFieldAttribute 属性を適用します。新規フィールドが含まれないストリームを処理する際、BinaryFormatter および SoapFormatter はこのフィールドの不足を無視します。
ISerializable インターフェイスの実装
シリアル化を制御するもう 1 つの方法は、オブジェクトに ISerializable インターフェイスを実装することです。ただし、シリアル化の制御においては、前のセクションで説明した方法のほうがこの方法より優先されることに注意してください。
また、Serializable 属性を使ってマークされ、クラス レベルまたはクラスのコンストラクタで宣言セキュリティまたは強制セキュリティが設定されたクラスでは既定のシリアル化を使用しないでください。代わりに、このようなクラスでは常に ISerializable インターフェイスを実装する必要があります。
ISerializable を実装すると、GetObjectData メソッドと、このオブジェクトが逆シリアル化されるときに使用される専用のコンストラクタも実装されます。次のサンプル コードは、前のセクションで使用した MyObject
クラスに ISerializable を実装する方法を示します。
[Serializable]
public class MyObject : ISerializable
{
public int n1;
public int n2;
public String str;
public MyObject()
{
}
protected MyObject(SerializationInfo info, StreamingContext context)
{
n1 = info.GetInt32("i");
n2 = info.GetInt32("j");
str = info.GetString("k");
}
[SecurityPermissionAttribute(SecurityAction.Demand,SerializationFormatter
=true)]
public virtual void GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue("i", n1);
info.AddValue("j", n2);
info.AddValue("k", str);
}
}
シリアル化のときに GetObjectData を呼び出す場合には、メソッド呼び出しで提供される SerializationInfo を設定する必要があります。これには、シリアル化の対象とする変数を名前と値の組み合わせとして追加します。名前には、任意のテキストを使用できます。逆シリアル化によってオブジェクトを復元するために十分なデータがシリアル化される場合、SerializationInfo に追加するメンバ変数は自由に決定できます。派生クラスの基本オブジェクトが ISerializable を実装している場合、派生クラスでは、基本オブジェクトの GetObjectData メソッドを呼び出す必要があります。
シリアル化によって、他の方法ではアクセスできないオブジェクト インスタンス データを他のコードから参照または変更できるようになります。したがって、シリアル化を実行するコードでは、SecurityPermission と共に SerializationFormatter フラグが指定されている必要があります。既定のポリシーでは、インターネットからダウンロードしたコードまたはイントラネット コードにはこのアクセス権は付与されず、ローカル コンピュータ上のコードだけに与えられます。GetObjectData メソッドは、SecurityPermission と共に SerializationFormatter フラグの指定を要求するか、プライベート データの保護に役立つ他の特別なアクセス許可を要求することによって、明示的に保護する必要があります。
プライベート フィールドに機密情報が格納されている場合は、GetObjectData で適切なアクセス許可を要求してデータを保護してください。SecurityPermission が付与され、SerializationFormatter フラグが指定されたコードは、プライベート フィールドに格納されたデータを参照および変更できます。悪意のある呼び出し元にこの SecurityPermission が付与されている場合は、この呼び出し元によって、隠しディレクトリの位置や付与されたアクセス許可などのデータが参照され、コンピュータ上のセキュリティの脆弱性が利用される可能性があります。指定できるセキュリティ アクセス許可フラグすべての一覧については、「SecurityPermissionFlag 列挙体」を参照してください。
ISerializable をクラスに追加する場合は、GetObjectData と専用のコンストラクタの両方を実装する必要があります。GetObjectData が指定されていない場合は、コンパイラから警告が出力されます。ただし、コンストラクタを強制的に実装させることはできないため、コンストラクタが指定されていなくても警告は表示されず、コンストラクタのないクラスの逆シリアル化が試行された時点で例外がスローされます。
現在のデザインは、セキュリティやバージョン管理に関して発生する可能性がある問題を回避するために、SetObjectData メソッドよりも優先されています。たとえば SetObjectData メソッドは、インターフェイスの一部として定義された場合には public である必要があるため、ユーザーは SetObjectData メソッドが複数回呼び出されることを防ぐようにコードを記述する必要があります。そうしないと、悪意のあるアプリケーションが、操作を実行しているオブジェクトの SetObjectData メソッドを呼び出すことによって、さまざまな問題が発生する可能性があります。
逆シリアル化のときには、SerializationInfo は、これをクラスに渡すために提供されている専用のコンストラクタを使用してクラスに渡されます。オブジェクトが逆シリアル化されるときには、コンストラクタに対して設定された参照可能範囲の制限は無視されるため、クラスは public、protected、internal、または private としてマークできます。クラスがシール クラスである場合を除き、コンストラクタを protected にすることがベスト プラクティスです。ただし、この場合はコンストラクタを private にする必要があります。コンストラクタは入力の検証も実行する必要があります。悪意のあるコードの不正な使用を回避するために、コンストラクタは、他のコンストラクタを使用するクラスのインスタンスを取得する場合と同様のセキュリティ チェックおよびアクセス許可を適用する必要があります。そうしないと、パブリック コンストラクタによる通常のインスタンスの生成時に適用されるはずのセキュリティが適用されず、悪意のあるコードが、オブジェクトを事前にシリアル化し、SecurityPermission と SerializationFormatter フラグの指定によって制御を取得して、クライアント コンピュータ上でオブジェクトを逆シリアル化することが可能になります。
オブジェクトの状態を復元するには、シリアル化のときに使用した名前を使って、SerializationInfo から変数の値を取得します。基本クラスに ISerializable が実装されている場合は、基本オブジェクトがその変数を復元できるようにするために、基本コンストラクタを呼び出す必要があります。
ISerializable を実装しているクラスから新しいクラスを派生させる場合は、派生クラスにシリアル化する必要のある変数があると、派生クラスにはコンストラクタと GetObjectData メソッドの両方を実装する必要があります。コンストラクタとメソッドの両方を実装する方法を、前のセクションで説明した MyObject
クラスを使用して次のコード例で示します。
[Serializable]
public class ObjectTwo : MyObject
{
public int num;
public ObjectTwo() : base()
{
}
protected ObjectTwo(SerializationInfo si, StreamingContext context) : base(si,context)
{
num = si.GetInt32("num");
}
[SecurityPermissionAttribute(SecurityAction.Demand,SerializationFormatter
=true)]
public override void GetObjectData(SerializationInfo si, StreamingContext context)
{
base.GetObjectData(si,context);
si.AddValue("num", num);
}
}
逆シリアル化コンストラクタで、必ず基本クラスを呼び出すようにしてください。そうしないと、基本クラスのコンストラクタが呼び出されず、逆シリアル化の後にオブジェクトが完全に構築されません。
オブジェクトは内側から外側に向かって再構築されるため、逆シリアル化のときに呼び出しを行うメソッドは、望ましくない副作用を引き起こす可能性があります。これは、呼び出しを行うメソッドが、呼び出しの時点では逆シリアル化されていないオブジェクト参照を参照する可能性があるためです。逆シリアル化対象のクラスが IDeserializationCallback を実装している場合は、オブジェクト グラフ全体が逆シリアル化された時点で OnDeserialization メソッドが自動的に呼び出されます。この時点で、参照されているすべての子オブジェクトが完全に復元されます。ハッシュ テーブルは、イベント リスナを使用せずに逆シリアル化することが困難なクラスの典型的な例です。逆シリアル化の実行中にキーと値の組み合わせを取得することは簡単ですが、これらのオブジェクトをハッシュ テーブルに戻すと、このハッシュ テーブルから派生されたクラスが逆シリアル化されているかどうかわからないため、問題が発生する可能性があります。そのため、この段階でハッシュ テーブル上のメソッドを呼び出すことは適切ではありません。
参照
概念
その他の技術情報
バイナリ シリアル化
リモート オブジェクト
XML シリアル化および SOAP シリアル化
コード アクセス セキュリティ