这里的答案是“视情况而定”。 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<Person> 正常工作 - 我需要对此进行调查。可能有一个很好的理由,但我目前想不出一个,我怀疑这是一个错误。
AsReference 也可以包含在 string 成员中以避免重复写入相同的字符串 - 尽管请注意,在这种情况下,两次写入 "Mike" 可能更便宜!当相同的string 重复很多 次时,此选项将很有用。尽管有这个名字,但在使用 string 时,AsReference 被解释为“字符串相等”,而不是“引用相等”。