カスタム バイナリシリアル化

カスタムのシリアル化は、型のシリアル化と逆シリアル化を制御するプロセスです。 シリアル化を制御することで、シリアル化の互換性を保証できます。つまり、型のコア機能を損なうことなく、1 つの型の複数のバージョン間でシリアル化および逆シリアル化を行うことができます。 たとえば、最初のバージョンの型では、フィールドが 2 つだけあるとします。 新しいバージョンでは、これにいくつかのフィールドが追加されています。 この場合、2 番目のバージョンのアプリケーションでは、両方の型をシリアル化および逆シリアル化できる必要があります。 以下のセクションでは、シリアル化の制御方法について説明します。

警告

バイナリ シリアル化は危険です。 詳しくは、「BinaryFormatter セキュリティ ガイド」をご覧ください。

重要

.NET Framework 4.0 より前のバージョンでは、部分的に信頼されたアセンブリでのカスタム ユーザー データのシリアル化は GetObjectData を使用して実行されていました。 バージョン 4.0 から 4.8 の .NET Framework では、そのメソッドは、部分的に信頼されたアセンブリで実行できないようにする SecurityCriticalAttribute 属性でマークされています。 この状況に対処するには、ISafeSerializationData インターフェイスを実装します。

シリアル化中およびシリアル化後にカスタム メソッドを実行する

バイナリシリアル化中およびバイナリシリアル化後にカスタム メソッドを実行するには、シリアル化中およびシリアル化後にデータを修正するために使用されるメソッドに次の属性を適用することをお勧めします。

これらの属性を適用すると、シリアル化プロセスと逆シリアル化プロセスの 4 つのフェーズのうち、いずれか、またはすべてに型を参加させることができます。 これらの属性は、各フェーズで呼び出す必要がある型のメソッドを指定します。 これらのメソッドはシリアル化ストリームにはアクセスしませんが、これらを使用すると、シリアル化の前後、または逆シリアル化の前後にオブジェクトを変更できます。 これらの属性は型の継承階層の全レベルで適用でき、各メソッドは、基本クラスから最派生クラスまで、階層内で呼び出されます。 このしくみでは、最派生実装でシリアル化および逆シリアル化が行われるため、ISerializable インターフェイスの実装の複雑性やその実装によって発生する問題が回避されます。 また、フォーマッタは、フィールド値の設定およびシリアル化ストリームからの取得を無視できます。 シリアル化および逆シリアル化の制御の詳細と例については、上記の各リンクをクリックしてください。

また、既存のシリアル化可能な型に新しいフィールドを追加する場合は、このフィールドに OptionalFieldAttribute 属性を適用します。 新しいフィールドが含まれていないストリームを処理する際に、BinaryFormatter および SoapFormatter はこのフィールドの不足を無視します。

ISerializable インターフェイスを実装する

バイナリ シリアル化を制御するもう 1 つの方法は、 ISerializable オブジェクトに インターフェイスを実装することです。 ただし、シリアル化の制御では、前のセクションで説明した方法がこの方法よりも優先されることに注意してください。

また、Serializable 属性を使用してマークされ、クラス レベルまたはクラスのコンストラクターで宣言セキュリティまたは強制セキュリティが設定されたクラスでは、既定のシリアル化を使用しないでください。 代わりに、このようなクラスでは、常に ISerializable インターフェイスを実装する必要があります。

ISerializable を実装すると、GetObjectData メソッドと、このオブジェクトが逆シリアル化されるときに使用される専用のコンストラクターも実装されます。 前のセクションで使用した ISerializable クラスに MyObject を実装する方法を次のコード例に示します。

[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");
    }

    public virtual void GetObjectData(SerializationInfo info, StreamingContext context)
    {
        info.AddValue("i", n1);
        info.AddValue("j", n2);
        info.AddValue("k", str);
    }
}
<Serializable()>  _
Public Class MyObject
    Implements ISerializable
    Public n1 As Integer
    Public n2 As Integer
    Public str As String

    Public Sub New()
    End Sub

    Protected Sub New(ByVal info As SerializationInfo, ByVal context As StreamingContext)
        n1 = info.GetInt32("i")
        n2 = info.GetInt32("j")
        str = info.GetString("k")
    End Sub 'New

    Public Overridable Sub GetObjectData(ByVal info As SerializationInfo, ByVal context As StreamingContext)
        info.AddValue("i", n1)
        info.AddValue("j", n2)
        info.AddValue("k", str)
    End Sub
End Class

GetObjectDataシリアル化中に が呼び出された場合は、 メソッド呼び出しで指定された をSerializationInfo設定する必要があります。 シリアル化の対象とする変数は、名前と値のペアとして追加します。 名前には、任意のテキストを使用できます。 逆シリアル化時にオブジェクトを復元するのに十分なデータがシリアル化される場合は、SerializationInfo に追加するメンバー変数を自由に決定できます。 派生クラスは、基本オブジェクトで メソッドを GetObjectData 呼び出す必要があります。後者が を実装している場合は ISerializable

クラスに を追加するときは ISerializable 、 と 特別なコンストラクターの両方 GetObjectData を実装する必要があることを強調することが重要です。 が見つからない場合 GetObjectData 、コンパイラから警告が表示されます。 ただし、コンストラクターの実装を強制することはできないため、コンストラクターが指定されていなくても警告は表示されず、コンストラクターがないクラスの逆シリアル化が試行された時点で例外がスローされます。

セキュリティやバージョン管理に関して発生する可能性がある問題を回避するために、現在のデザインは SetObjectData メソッドよりも優先されています。 たとえば、インターフェイスの SetObjectData 一部として定義されている場合、メソッドはパブリックである必要があります。したがって、ユーザーは メソッドを複数回呼び出すのを防御するコードを SetObjectData 記述する必要があります。 そうしないと、操作の実行中にオブジェクトで メソッドを SetObjectData 呼び出す悪意のあるアプリケーションが、潜在的な問題を引き起こす可能性があります。

逆シリアル化時に、SerializationInfo は、これをクラスに渡すために提供されているコンストラクターを使用してクラスに渡されます。 オブジェクトが逆シリアル化されるときには、コンストラクターに対して設定された参照可能範囲の制限は無視されるため、パブリック、プロテクト、内部、またはプライベートとしてクラスをマークできます。 ただし、クラスがシールされている場合を除いて、コンストラクターをプロテクトにすることがベスト プラクティスです。クラスがシールされている場合は、コンストラクターをプライベートとマークする必要があります。 コンストラクターは入力の検証も実行する必要があります。

オブジェクトの状態を復元するには、シリアル化時に使用した名前を使って、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");
    }

    public override void GetObjectData(SerializationInfo si, StreamingContext context)
    {
        base.GetObjectData(si,context);
        si.AddValue("num", num);
    }
}
<Serializable()>  _
Public Class ObjectTwo
    Inherits MyObject
    Public num As Integer

    Public Sub New()

    End Sub

    Protected Sub New(ByVal si As SerializationInfo, _
    ByVal context As StreamingContext)
        MyBase.New(si, context)
        num = si.GetInt32("num")
    End Sub

    Public Overrides Sub GetObjectData(ByVal si As SerializationInfo, ByVal context As StreamingContext)
        MyBase.GetObjectData(si, context)
        si.AddValue("num", num)
    End Sub
End Class

必ず、逆シリアル化コンストラクターで基本クラスを呼び出すようにしてください。 そうしないと、基本クラスのコンストラクターが呼び出されず、逆シリアル化後にオブジェクトが完全には構築されません。

オブジェクトは内側から外側に向かって再構築されるため、逆シリアル化時にメソッドを呼び出すと、望ましくない副作用を引き起こす可能性があります。これは、呼び出されるメソッドが、呼び出しの時点では逆シリアル化されていないオブジェクト参照を参照することがあるためです。 逆シリアル化対象のクラスで IDeserializationCallback を実装する場合、オブジェクト グラフ全体が逆シリアル化された時点で OnDeserialization メソッドが自動的に呼び出されます。 この時点で、参照されているすべての子オブジェクトが完全に復元されます。 ハッシュ テーブルは、イベント リスナーを使用せずに逆シリアル化することが困難なクラスの典型的な例です。 逆シリアル化時にキーと値のペアを取得することは簡単ですが、これらのオブジェクトをハッシュ テーブルに戻すと、このハッシュ テーブルから派生したクラスが逆シリアル化されているかどうかわからないため、問題が発生する可能性があります。 したがって、この段階でハッシュ テーブルのメソッドを呼び出すことはお勧めできません。

関連項目