.net 解析实现ISerializable的对象的循环引用

.net 解析实现ISerializable的对象的循环引用,.net,serialization,circular-reference,iserializable,.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

我正在编写自己的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);
    }
}
然后我会这样做:

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注入新数据


有人能给我指出正确的方向吗?

您需要检测到您在对象图中多次使用同一对象,在输出中标记每个对象,并且当达到发生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
反序列化的伪代码:

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
someobjectobj=newsomeobject();

obj.ReferenceToSomeObject=obj 这种情况就是采用这种方法的原因。一般的想法是,如果对象A和B在其
序列化信息中相互引用,则可以按如下方式反序列化它们:

(在本解释中,
(SI,SC)
指类型的反序列化构造函数,即采用
序列化信息和
流化上下文的构造函数)

  • 首先选择一个要反序列化的对象。选择哪一个并不重要,只要不选择值类型。假设你选了一个
  • 调用
    GetUninitializedObject
    分配(但不初始化)A类型的实例,因为您还没有准备好调用其
    (SI,SC)
    构造函数
  • 以通常的方式构建B,即创建一个
    SerializationInfo
    对象(其中将包括对现在半反序列化的a的引用),并将其传递给B的
    (SI,SC)
    构造函数
  • 现在,您已经拥有了初始化分配给您的对象所需的所有依赖项。创建它的
    SerializationInfo
    对象并调用A的
    (SI,SC)
    构造函数。您可以通过反射对现有实例调用构造函数
  • GetUninitializedObject
    方法是纯粹的CLR魔法——它创建实例时从未调用构造函数来初始化该实例。它基本上将所有字段设置为零/空

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

    该类可以为您完成所有这些(以及其他类型的修复)。然而,考虑到反序列化的复杂性,我总是发现它的文档太少,所以我从来没有花时间去尝试如何正确地使用它。它使用了一些更神奇的方法来完成第4步,使用一些内部的CLR反射优化来更快地调用
    (SI,SC)
    构造函数(我的计时速度大约是公共方式的两倍)


    最后,还有一些对象图涉及无法反序列化的循环。一个例子是当您有两个
    IObjectReference
    实例的循环时(我已经测试了
    BinaryFormatter
    ,它抛出了一个异常)。另一个我怀疑的问题是你是否有一个。

    我理解你关于“引用标记”的观点,我的格式化程序已经使用了这种技术。因此,您的自我参照示例对我来说不是问题。但我不明白您的答案如何帮助我实现相互引用的ISerializable实现对象。你能解决这个具体问题吗?谢谢。我不完全明白你的意思。你说的是使用与序列化相关的私有构造函数吗?是的,没错。膨胀实现ISerializable的对象的唯一方法是调用其特殊构造函数。我不知道这是如何工作的。要么你将“反序列化”一个代理对象(不知道怎么做),要么你必须在调用实际构造函数之前构造一个空对象(也不知道怎么做)。从BinaryFormatter的行为判断,根据需要创建并传递对象引用。随后,对同一对象引用调用ISerializable构造函数。不过,我不知道这是怎么做到的。