使用循环引用序列化对象图的方法

时间:2016-11-08 09:14:55

标签: c# serialization circular-reference

假设我想为了练习而实现一个序列化器(C#),我希望所述序列化器不会因循环引用而失败。

明显的解决方案是仅序列化尚未遇到的对象并跳过对象。这可以通过对实例进行散列(以某种方式)来轻松实现。

建议的解决方案出价问题:"什么定义了对象的身份?" 有人会说 - 把它留给GetHashCode和Equals方法。 这是一个可接受的解决方案 - 它可以节省序列化时间并在反序列化时节省内存。

然而,这并不总是理想的结果,因为许多实例可能具有相同的身份但尚未用于序列化域中完全不同的事物,因此稍后将它们反序列化为同一实例将违反域逻辑。

因此,作为此类序列化程序的作者,我必须让调用者做出这样的决定。

解决此问题的一种方法是按照所述类型散列集合,并通过迭代集合并在每个包含的元素上调用ReferenceEquals来区分序列化和非序列化实例。 这是有效的,但不是最佳的 - 性能明智。

另一种方法是将对象固定在非托管堆中,并使用固定对象地址作为标识,这看起来有点过分,并且还有很多开销。

另一种方法是使用反射来调用每个实例的Object.Equals和Object.GetHashCode默认实现 - 这似乎可以解决问题,但是它有自己的小开销。

我的问题是:

1)对于我建议的方法,我是否有任何警告? 2)还有其他方法我可能没有想过吗?

3 个答案:

答案 0 :(得分:1)

看看System.Runtime.Serialization.ObjectIDGenerator。它正是如此。

根据MSDN页面:

  

使用哈希表,ObjectIDGenerator会保留为哪个对象分配的ID。唯一标识每个对象的对象引用是运行时垃圾收集堆中的地址。对象引用值可以在序列化期间更改,但表会自动更新,以便信息正确。

源代码也可用here

答案 1 :(得分:0)

实际上导致循环引用(即应用程序中的无限循环)的唯一内容是实际的对象引用。所以不要保留哈希列表,保留以前遇到的对象列表。

如果你想保持序列化数据尽可能小,你可以实现它类似于nuget组织packages文件夹的方式 - 将每个对象写出一次,但是当一个对象引用另一个对象时,写一些类型的引用键。

[
    {
        serialisationKey: "GUID1",
        name: "Neil",
        friends: [
            { obj: "GUID2" },
            { obj: "GUID3" }
        ]
    },
    {
        serialisationKey: "GUID2",
        name: "Bob",
        friends: [
            { obj: "GUID1" }
        ]
    },
    {
        serialisationKey: "GUID3",
        name: "Alf",
        friends: [
            { obj: "GUID1" }
        ]
    }
]

答案 2 :(得分:0)

不要固定内存!您可以使用object.ReferenceEquals

您的序列化程序不应该是智能的,并尝试确定是否需要将同一对象序列化为一个对象或两个对象。序列化每个对象一次 - 如果对象被引用两次,则在序列化数据中引用它两次。