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

Chr*_*ris 3 .net serialization circular-reference iserializable

我正在编写自己的IFormatter实现,我想不出一种方法来解决两个实现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);
    }
}
Run Code Online (Sandbox Code Playgroud)

然后我这样做:

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);
Run Code Online (Sandbox Code Playgroud)

如果我使用开箱即用的BinaryFormatter来序列化和反序列化b,那么引用相等的上述测试会像人们期望的那样返回true.但是我无法想象在我的自定义IFormatter中实现这一目标的方法.

在非ISerializable情况下,一旦目标引用被解析,我就可以使用反射简单地重新访问"待定"对象字段.但是对于实现ISerializable的对象,不可能使用SerializationInfo注入新数据.

谁能指出我正确的方向?

Wes*_*ill 5

这种情况是该FormatterServices.GetUninitializedObject方法的原因.一般的想法是,如果你有对象A和B在它们中相互引用SerializationInfo,你可以按如下方式反序列化它们:

(为了解释的目的,(SI,SC)引用一个类型的反序列化构造函数,即带有a SerializationInfo和a的那个StreamingContext.)

  1. 首先选择一个对象进行反序列化.只要你不挑选一个值类型的东西,你选择哪个都没关系.让我们说你选A.
  2. 打电话GetUninitializedObject分配(但不初始化)的类的实例,因为你还没有准备调用它的(SI,SC)构造函数.
  3. 以通常的方式构建B,即创建一个SerializationInfo对象(将包括对现在半反序列化的A的引用)并将其传递给B的(SI,SC)构造函数.
  4. 现在,您拥有了初始化已分配A对象所需的所有依赖项.创建它的SerializationInfo对象并调用A的(SI,SC)构造函数.您可以通过反射在现有实例上调用构造函数.

GetUninitializedObject方法是纯粹的CLR魔法 - 它创建一个实例,而无需调用构造函数来初始化该实例.它基本上将所有字段设置为零/ null.

这是警告您不要在(SI,SC)构造函数中使用子对象的任何成员的原因- 可以分配子对象但在此时尚未初始化.这也是IDeserializationCallback接口的原因,它使您有机会在保证完成所有对象初始化之后和返回反序列化对象图之前使用子对象.

的ObjectManager类能为你做这一切(和其他类型的固定起坐).但是,考虑到反序列化的复杂性,我总是发现它的文档记录很少,所以我从来没有花时间试图弄清楚如何正确使用它.它使用一些更多的魔法来执行第4步,使用一些优化的内部到CLR反射来(SI,SC)更快地调用构造函数(我的时间大约是公共方式的两倍).

最后,有些对象图涉及无法反序列化的循环.一个例子是当你有一个两个IObjectReference实例的循环时(我已经BinaryFormatter对此进行了测试并抛出异常).另一个我怀疑的是,如果你的周期只涉及盒装值类型.