解决的对象实现ISerializable的循环引用对象、ISerializable

2023-09-03 20:59:57 作者:纯天然绿色学渣

我在写我自己的IFormatter实施,我不能想办法解决两种类型之间的循环引用都实现了ISerializable。

I'm writing my own IFormatter implementation and I cannot think of a way to resolve circular references between two types that both implement ISerializable.

下面是通常的模式:

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

然后我做到这一点:

I then do this:

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来序列化和deserialise B,以供参考平等上面的测试返回true,正如人们所期望的那样。但我无法想象的方式在我的自定义IFormatter来实现这一点。

If I use an out-of-the-box BinaryFormatter to serialise and deserialise b, the above test for reference-equality returns true as one would expect. But I cannot conceive of a way to achieve this in my custom IFormatter.

在非ISerializable的情况下,我可以简单地重新审视待定使用反射对象字段一旦目标引用都得到了解决。但用于实现ISerializable的对象,不可能使用SerializationInfo中注​​入新的内容

In a non-ISerializable situation I can simply revisit "pending" object fields using reflection once the target references have been resolved. But for objects implementing ISerializable it is not possible to inject new data using SerializationInfo.

任何人都可以点我朝着正确的方向?

Can anyone point me in the right direction?

推荐答案

这种情况的原因FormatterServices.GetUninitializedObject方法。总的想法是,如果你有对象A和B相互引用其的SerializationInfo ,你可以反序列化它们如下:

This situation is the reason for the FormatterServices.GetUninitializedObject method. The general idea is that if you have objects A and B which reference each other in their SerializationInfo, you can deserialize them as follows:

(对于这种解释的目的,(SI,SC)是指一类的反序列化的构造,即其中一个需要的SerializationInfo 的StreamingContext

(For the purposes of this explanation, (SI,SC) refers to a type's deserialization constructor, i.e. the one which takes a SerializationInfo and a StreamingContext.)

选择一个对象,首先要反序列化。它不应该的问题,你挑,只要你不挑人是一个价值型。比方说,你挑一个。 呼叫 GetUninitializedObject 来的分配的(但不初始化)A的类型实例,因为你还没有准备好打电话给其(SI,SC)的构造。 在构建乙方在通常的方式,即创建一个的SerializationInfo 对象(其中包括引用到现在的半反序列化),并把它传递给B的(SI,SC)的构造。 现在,你有你需要的初始化的你分配一个对象的依赖关系。创建它的的SerializationInfo 对象,并调用A的(SI,SC)的构造。您可以通过反射调用现有的实例构造函数。 Pick one object to deserialize first. It shouldn't matter which you pick, as long as you don't pick one which is a value-type. Lets say you pick A. Call GetUninitializedObject to allocate (but not initialize) an instance of A's type, because you're not yet ready to call its (SI,SC) constructor. Build B in the usual way, i.e. create a SerializationInfo object (which will include the reference to the now half-deserialized A) and pass it to B's (SI,SC) constructor. Now you have all the dependencies you need to initialize your allocated A object. Create it's SerializationInfo object and call A's (SI,SC) constructor. You can call a constructor on an existing instance via reflection.

GetUninitializedObject 方法是纯粹的CLR法宝 - 它永远不会调用构造函数初始化该实例创建一个实例。它基本上将所有字段设为零/零。

The GetUninitializedObject method is pure CLR magic - it creates an instance without ever calling a constructor to initialize that instance. It basically sets all fields to zero/null.

这是你应注意不要使用任何一个子对象的成员在(SI,SC)构造的原因 - 一个子对象可以分配,但尚未初始化在该点。这也是对 IDeserializationCallback 接口的原因,它给你使用后,所有的对象初始化是保证完成和反序列化对象图返回之前,你的子对象的机会

This is the reason you are cautioned not to use any of the members of a child object in a (SI,SC) constructor - a child object may be allocated but not yet initialized at that point. It is also the reason for the IDeserializationCallback interface, which gives you a chance to use your child objects after all object initialization is guaranteed to be done and before the deserialized object graph is returned.

的ObjectManager类可以为你做这一切(和其他类型的固定窗口)。但是,我一直认为这是相当不足的文件中给出的反序列化的复杂性,所以我从来没有花时间去尝试找出如何正确使用它。它使用一些魔法使用一些内部到最CLR反射优化调用(SI,SC)构造函数更快地执行步骤4(我已经在定时它大约快一倍的公开的方式)。

The ObjectManager class can do all of this (and other types of fix-ups) for you. However, I've always found it to be quite under-documented given the complexity of deserialization, so I never spent the time to try figure out how to use it properly. It uses some more magic to do step 4 using some internal-to-the-CLR reflection optimized to call the (SI,SC) constructor quicker (I've timed it at about twice as fast as the public way).

最后,还有一些涉及循环这是不可能的反序列化对象图。一个例子是,当你有两个 IObjectReference 实例(我测试过的BinaryFormatter 的周期就这个问题和它引发例外)。另外我怀疑是,如果你有一个循环中涉及无非是装箱的值类型的。

Finally, there are object graphs involving cycles which are impossible to deserialize. One example is when you have a cycle of two IObjectReference instances (I've tested BinaryFormatter on this and it throws an exception). Another I suspect is if you have a cycle involving nothing but boxed value-types.