3

私は独自の IFormatter 実装を作成していますが、両方とも ISerializable を実装する 2 つの型の間の循環参照を解決する方法が思いつきません。

通常のパターンは次のとおりです。

[Serializable]
class Foo : ISerializable
{
    private Bar m_bar;

    public Foo(Bar bar)
    {
        m_bar = bar;
        m_bar.Foo = this;
    }

    public Bar Bar
    {
        get { return m_bar; }
    }

    protected Foo(SerializationInfo info, StreamingContext context)
    {
        m_bar = (Bar)info.GetValue("1", typeof(Bar));
    }

    public void GetObjectData(SerializationInfo info, StreamingContext context)
    {
        info.AddValue("1", m_bar);
    }
}

[Serializable]
class Bar : ISerializable
{
    private Foo m_foo;

    public Foo Foo
    {
        get { return m_foo; }
        set { m_foo = value; }
    }

    public Bar()
    { }

    protected Bar(SerializationInfo info, StreamingContext context)
    {
        m_foo = (Foo)info.GetValue("1", typeof(Foo));
    }

    public void GetObjectData(SerializationInfo info, StreamingContext context)
    {
        info.AddValue("1", m_foo);
    }
}

次に、これを行います。

Bar b = new Bar();
Foo f = new Foo(b);
bool equal = ReferenceEquals(b, b.Foo.Bar); // true

// Serialise and deserialise b

equal = ReferenceEquals(b, b.Foo.Bar);

すぐに使用できる BinaryFormatter を使用して b をシリアル化および逆シリアル化すると、上記の参照等価性のテストは予想どおり true を返します。しかし、カスタム IFormatter でこれを実現する方法が思いつきません。

ISerializable でない状況では、ターゲット参照が解決されたら、リフレクションを使用して「保留中」のオブジェクト フィールドに簡単に再アクセスできます。ただし、ISerializable を実装するオブジェクトの場合、SerializationInfo を使用して新しいデータを挿入することはできません。

誰かが私を正しい方向に向けることができますか?

4

2 に答える 2

5

この状況がこの方法の理由ですFormatterServices.GetUninitializedObject。一般的な考え方は、 で相互に参照するオブジェクト A と B がある場合SerializationInfo、次のように逆シリアル化できるということです。

(この説明では、 は型の逆シリアル化コンストラクター、つまり aと a(SI,SC)を取るコンストラクターを指します。)SerializationInfoStreamingContext

  1. 最初に逆シリアル化するオブジェクトを 1 つ選択します。値型を選択しない限り、どちらを選択してもかまいません。Aを選んだとしましょう。
  2. GetUninitializedObjectA の型のインスタンスを割り当てる(ただし初期化しない) ために呼び出します。これは、その(SI,SC)コンストラクターを呼び出す準備がまだ整っていないためです。
  3. 通常の方法で B をビルドします。つまり、SerializationInfoオブジェクトを作成し (半分デシリアライズされた A への参照が含まれます)、それを B の(SI,SC)コンストラクターに渡します。
  4. これで、割り当てられた A オブジェクトを初期化するために必要な依存関係がすべて揃いました。そのオブジェクトを作成し、A のコンストラクターSerializationInfoを呼び出します。(SI,SC)リフレクションを介して、既存のインスタンスでコンストラクターを呼び出すことができます。

このGetUninitializedObjectメソッドは純粋な CLR マジックです。コンストラクターを呼び出してインスタンスを初期化することなく、インスタンスを作成します。基本的に、すべてのフィールドをゼロ/ヌルに設定します。

これが、コンストラクターで子オブジェクトのメンバーを使用しないように注意する理由です。(SI,SC)子オブジェクトは割り当てられても、その時点ではまだ初期化されていない可能性があります。IDeserializationCallbackこれは、すべてのオブジェクトの初期化が保証された後、デシリアライズされたオブジェクト グラフが返される前に、子オブジェクトを使用する機会を与えるインターフェイスの理由でもあります。

ObjectManagerクラスは、これらすべて (およびその他の種類の修正) を実行できます。ただし、デシリアライゼーションの複雑さを考えると、ドキュメントがかなり不十分であることが常にわかっていたので、適切に使用する方法を理解するために時間を費やしたことはありません. コンストラクターをより速く呼び出すために最適化された CLR への内部リフレクションを使用して、ステップ 4 を実行するためにさらにいくつかの魔法を使用し(SI,SC)ます (パブリックな方法の約 2 倍の速さでタイミングを計りました)。

最後に、デシリアライズできないサイクルを含むオブジェクト グラフがあります。1 つの例は、2 つのインスタンスのサイクルがある場合ですIObjectReference(これについてテストBinaryFormatterしたところ、例外がスローされました)。もう1つは、ボックス化された value-types だけを含むサイクルがある場合です。

于 2010-09-07T13:54:46.793 に答える
0

オブジェクト グラフで同じオブジェクトを複数回使用したことを検出し、出力内の各オブジェクトにタグを付ける必要があります。出現 #2 以上になったら、代わりに既存のタグへの「参照」を出力する必要があります。オブジェクトのもう一度。

シリアル化の擬似コード:

for each object
    if object seen before
        output tag created for object with a special note as "tag-reference"
    else
        create, store, and output tag for object
        output tag and object

逆シリアル化の擬似コード:

while more data
    if reference-tag to existing object
        get object from storage keyed by the tag
    else
        construct instance to deserialize into
        store object in storage keyed by deserialized tag
        deserialize object

このケースを正しく処理できるように、指定された順序で最後の手順を実行することが重要です。

SomeObject obj = new SomeObject();
obj.ReferenceToSomeObject = obj;    <-- reference to itself

すなわち。オブジェクトを完全に逆シリアル化した後は、オブジェクトをタグ ストレージに格納することはできません。

于 2010-04-26T10:26:05.647 に答える