【问题标题】:Understanding the concept of 'serializing by reference'理解“通过引用序列化”的概念
【发布时间】:2015-05-01 01:47:40
【问题描述】:

我正在编写自己的二进制序列化程序,并针对游戏开发进行了优化。到目前为止,它功能齐全。它发出 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 时? - 是“通过引用序列化” 仅适用于单个对象层次结构,或者它是如何工作的 一般?
  2. 在我看来,通过引用进行序列化会自动 处理循环引用(A B),对吗?还是我需要 做其他事情以确保我不会陷入无限循环?
  3. 我认为通过引用进行序列化只有在应用时才有意义 关于引用类型而不是值类型,对吗?

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

【问题讨论】:

标签: c# serialization protobuf-net datacontractserializer


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

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

【讨论】:

  • 感谢您的回复。 On 1-这是所有通过 ref 实现序列化的序列化器的标准吗?还是仅在 DataContractSerializer 中?这是否意味着示例 2 和示例 3 中无法通过引用序列化第二个对象?或者这没有意义,这是我误解的一部分?
  • @vexe 这在所有序列化程序中几乎是标准的;您可能能够找到一个具有用于保存此类 API 的 API,但这不是预期的情况;它期望每次调用Serialize / Write / 无论是无关的和独立的。特别是,如果您调用Write(obj, target1)Write(obj, target2),通常预计target1target2 将具有相同 内容(或至少:相同的内容)。如果第二次只是说“我以前见过这些对象:我将序列化 id”,则情况并非如此。
  • 我在考虑使用 protobuf-net 并为带有标记为 [AsReference] 的字段的类型编译一个 dll(如果我没记错的话),然后查看生成的代码。它应该将“通过引用序列化”逻辑烘焙到 dll 中吗?所以我也许可以大致了解如何去做
  • @vexe 的逻辑存在于核心库中,而不是生成的 IL。 基本上有一个引用键字典
  • 谢谢。最后,您是否推荐任何深入研究该主题的阅读/书籍?我阅读的所有内容都教您如何使用现有的序列化程序,而不是它们如何在内部工作/如何自己实现。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-02-18
  • 1970-01-01
  • 1970-01-01
  • 2020-06-25
  • 1970-01-01
  • 2013-10-20
相关资源
最近更新 更多