协议缓冲区是否支持轻量级引用序列化

本文关键字:引用 序列化 轻量级 支持 缓冲区 是否 协议 | 更新日期: 2023-09-27 18:31:01

>假设一个这样的类:

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的唯一引用,然后解析它,还是将序列化两次?

协议缓冲区是否支持轻量级引用序列化

这里的答案是"视情况而定"。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 级):人

然而!作为特定于库的实现细节,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<Person> - 我需要对此进行调查。可能有一个很好的理由,但我目前想不出一个,我怀疑这是一个错误。

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