了解 'serializing by reference' 的概念

Understanding the concept of 'serializing by reference'

我正在编写自己的针对游戏开发优化的二进制序列化程序。到目前为止,它功能齐全。它发出 IL 以生成预先给定一系列类型的 [反] 序列化方法。唯一缺少的功能是通过引用序列化事物,目前一切都按值序列化。

要实现,我得先了解一下。这就是我发现有点棘手的地方。让我告诉你我在这几个例子中的理解:

示例 1(见 here):

public class Person
{
    public string Name;
    public Person Friend;
}

static void Main(string[] args)
{
    Person p1 = new Person();
    p1.Name = "John";

    Person p2 = new Person();
    p2.Name = "Mike";

    p1.Friend = p2;

    Person[] group = new Person[] { p1, p2 };

    var serializer = new DataContractSerializer(group.GetType(), null, 
        0x7FFF /*maxItemsInObjectGraph*/, 
        false /*ignoreExtensionDataObject*/, 
        true /*preserveObjectReferences : this is where the magic happens */, 
        null /*dataContractSurrogate*/);

    serializer.WriteObject(Console.OpenStandardOutput(), group);
}

现在完全明白了。我们有一个根对象,它是数组,引用两个独特的人。 p1.Friend 恰好是 p2。因此,我们不是按值序列化 p1.Friend,而是存储一个指向我们已经序列化的 p2 的 id。

但是;看看第二个例子:

    static void Example2()
    {
        var p1 = new Person() { Name = "Diablo" };
        var p2 = new Person() { Name = "Mephesto" };

        p1.Friend = p2;

        var serializer = new DataContractSerializer(typeof(Person), null, 0x7FFF, false, true, null);

        serializer.WriteObject(Console.OpenStandardOutput(), p1);
        Console.WriteLine("\n");
        serializer.WriteObject(Console.OpenStandardOutput(), p2);
    }

现在,根据我的理解:当序列化 p1 时,序列化程序将序列化 p1.Namep1.Friend。在第二个 WriteObject 中,序列化程序已经序列化了 p2(即 p1.Friend),因此它只是序列化指向 p1.Friend 的 id,而不是按值序列化它。

运行 代码和查看输出似乎并非如此。在第二个输出中,我们看到序列化程序按值序列化 p2,就好像它还没有遇到它一样......我没有得到。就像内部有一个 ID 计数器在 WriteObject

结束时重置

这是另一个类似的例子:

    static void Example3()
    {
        var p1 = new Person() { Name = "Diablo" };
        var p2 = p1;

        var serializer = new DataContractSerializer(typeof(Person), null, 0x7FFF, false, true, null);

        serializer.WriteObject(Console.OpenStandardOutput(), p1);
        Console.WriteLine("\n");
        serializer.WriteObject(Console.OpenStandardOutput(), p2);
    }

同样,第二个输出显示我们正在序列化 p2,就好像我们还没有遇到它的定义一样。

请注意,我没有出于任何特定原因选择 DataContractSerializer,任何支持按引用序列化的序列化程序都可以工作。

我曾尝试在 DataContractSerializer 上使用 ILSpy,但我很快就迷路了,什么也搞不清楚。

  1. Example2中,为什么序列化器没有存储一个id到 p1.Friend 序列化时 p2? - 是 'serializing by reference' 仅适用于单个对象层次结构,或者它如何在 将军?
  2. 在我看来,按引用序列化会自动 处理循环引用 (A <-> B),对吗?或者我需要 做其他事情以确保我不会陷入无限循环?
  3. 我假设按引用序列化只有在应用时才有意义 关于引用类型而不是值类型,对吗?

我标记了 protobuf-net,因为它的相似之处在于它是一个二进制序列化程序并发出 IL。我很想听听那里是如何通过引用实现序列化的:p

  1. 每次调用 write-object 都是一个单独的序列化上下文;调用之间不保留引用跟踪
  2. 只要您正确识别以前看到的值,它就不会递归,但深度检查可以帮助避免问题
  3. 正确,尽管如果您愿意,您可以尝试识别语义相同的值类型(可能是结构相等接口)

其他想法:如果将其应用于字符串,您可能希望将特殊情况作为有效相等而不是引用相等 - 序列化两个不同的实例(引用)毫无意义相同的字符串