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

本文关键字:方法 对象图 序列化 循环 引用 | 更新日期: 2023-09-27 18:17:05

假设出于实践的考虑,我想实现一个序列化器(c#),并且我希望所述序列化器在循环引用时不会失败。

显而易见的解决方案是只序列化尚未遇到的对象,并跳过遇到的对象。这很容易通过对实例进行散列(以这样或那样的方式)来实现。

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

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

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

解决这个问题的一种方法是根据所述类型对集合进行散列,并通过迭代集合并在每个包含的元素上调用ReferenceEquals来区分序列化和非序列化实例。这是有效的,但是在性能方面不是最优的。

另一种方法是在非托管堆中固定对象,并使用固定的对象地址作为标识,这似乎有点多余,并且有很多开销。

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

我的问题是:

1)对于我所建议的方法,我是否遗漏了什么注意事项?
2)还有其他我没有想到的方法吗?

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

看一下system . runtime . serialize . objectidgenerator。

根据MSDN页面:

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

源代码也可以在这里获得

唯一会导致循环引用(即在你的应用程序中永无止境的循环)的是实际的对象引用。所以不要保存一个哈希列表,保存一个先前遇到的对象本身的列表。

如果你想保持序列化的数据尽可能小,你可以实现它类似于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" }
        ]
    }
]

不要记忆!您可以使用object.ReferenceEquals

你的序列化器不应该太聪明,试图弄清楚同一个对象是需要序列化为一个对象还是两个对象。序列化每个对象一次——如果对象被引用两次,在序列化的数据中引用它两次。