【问题标题】:Does protocol buffers support lightweight reference serialization协议缓冲区是否支持轻量级引用序列化
【发布时间】:2013-09-16 23:50:35
【问题描述】:

假设有这样一个类:

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

现在你创建了两个对象:

...
Person mike = new Person("Mike");
Person jack = new Person("Jack");
jack.Parent = mike;
List<Person> family = new List<Person>();
people.Add(mike);
people.Add(jack);
...

字符串“Mike”会被序列化一次(保持)对对象 mike 的唯一引用然后解析它,还是会被序列化两次?

【问题讨论】:

    标签: c# .net serialization protocol-buffers


    【解决方案1】:

    这里的答案是“视情况而定”。 protobuf 规范不包括任何对象标识符/重用,因此通常(默认情况下)这将是一个树序列化,并且数据将被复制。

    我们可以通过使用带有所有默认行为的 protobuf-net 来检查这一点:

    using ProtoBuf;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    
    class Program
    {
        static void Main()
        {
            Person mike = new Person { Name = "Mike" };
            Person jack = new Person { Name = "Jack" };
            jack.Parent = mike;
            List<Person> people = new List<Person>();
            people.Add(mike);
            people.Add(jack);
    
            var cloneOfEverything = Serializer.DeepClone(people);
    
            var newMike = cloneOfEverything.Single(x => x.Name == "Mike");
            var newJack = cloneOfEverything.Single(x => x.Name == "Jack");
            Console.WriteLine(jack.Parent.Name); // writes Miks as expected
    
            bool areSamePersonObject = ReferenceEquals(newMike, newJack.Parent);
            // False ^^^
    
            bool areSameStringInstance = ReferenceEquals(
                newMike.Name, newJack.Parent.Name);
            // True ^^^
        }
    }
    [ProtoContract]
    class Person
    {
        [ProtoMember(1)]
        public string Name;
        [ProtoMember(2)]
        public Person Parent;
    }
    

    观察:

    • 杰克的父母被正确地称为迈克
    • 但它是一个看起来完全相同的不同对象实例
    • string 是同一个实例 - 作为一个实现细节,protobuf-net 包含在数据中发现相同 UTF-8 块的代码,并重新使用相同的 string 实例以避免分配 - 但数据在二进制文件中包含两次
    • 信息,上面的树需要 24 个字节

    我们也可以通过调查这里发生的事情来看到这一点:

    Person mike = new Person { Name = "Mike" };
    mike.Parent = mike;
    var clone = Serializer.DeepClone(mike);
    

    因为它是写成一棵树,所以会出错:

    检测到可能的递归(偏移量:1 级):Person

    但是!作为特定于库的实现细节,protobuf-net 包含许多可以转动的旋钮和刻度盘。其中之一与对象身份有关。我们可以切换Person 以使用参考身份进行操作:

    [ProtoContract(AsReferenceDefault=true)]
    class Person {...}
    

    这更改了二进制文件中的数据(以包括额外的标记),因此 - 现在相同的行起作用了:

    Person mike = new Person { Name = "Mike" };
    mike.Parent = mike;
    var clone = Serializer.DeepClone(mike);
    bool areSamePersonObject = ReferenceEquals(clone, clone.Parent);
    // ^^^ true
    

    请注意,这使用了特定于实现的细节,并且可能会混淆其他实现。

    AsReferenceDefault 在这里声明Person 应被视为每当看到它的参考;对于更精细的控制,[ProtoMember] 还包括一个可以单独使用的AsReference 对象。然而,快速检查似乎表明它不目前与List&lt;Person&gt; 正常工作 - 我需要对此进行调查。可能有一个很好的理由,但我目前想不出一个,我怀疑这是一个错误。

    AsReference 也可以包含在 string 成员中以避免重复写入相同的字符串 - 尽管请注意,在这种情况下,两次写入 "Mike" 可能更便宜!当相同的string 重复很多 次时,此选项将很有用。尽管有这个名字,但在使用 string 时,AsReference 被解释为“字符串相等”,而不是“引用相等”。

    【讨论】:

    • 明白。所以简短的回答是“不正常”,因为我认为对于复杂的对象,事情可能会发生很大变化……对吧?
    • 简短的回答是“不正常”,但原因要简单得多:因为这不是规范所说的
    猜你喜欢
    • 2014-09-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-03-19
    • 1970-01-01
    • 2017-05-15
    • 2010-10-15
    • 1970-01-01
    相关资源
    最近更新 更多