使用递归引用理解 ProtoBuf-Net AsReference

Posted

技术标签:

【中文标题】使用递归引用理解 ProtoBuf-Net AsReference【英文标题】:Understanding ProtoBuf-Net AsReference with recursive referencing 【发布时间】:2011-08-25 05:40:36 【问题描述】:

我正在尝试使用 ProtoMember 中的 AsReference 选项进行递归引用。如果我使用公共构造函数创建 AnOwner 然后序列化/反序列化,AnOwner.Data 变为空。有人可以解释内部发生了什么以及是否支持递归引用?谢谢!

[ProtoContract()]
public class SomeData

    [ProtoMember(1, AsReference = true)]
    public AnOwner Owner;

    [ProtoMember(2)]
    public string Value;

    /// <summary>
    /// ProtoBuf deserialization constructor. In fact, Serializer did not complain when this is missing
    /// </summary>
    private SomeData()
    

    

    public SomeData(string value)
    
        Value = value;
    


[ProtoContract()]
public class AnOwner

    [ProtoMember(1)]
    public SomeData Data;

    /// <summary>
    /// ProtoBuf deserialization constructor
    /// </summary>
    private AnOwner()
    

    

    public AnOwner(SomeData data)
    
        Data = data;
        Data.Owner = this;
    

编辑: 经过深思熟虑,我设法以这个小演示的形式理解它,我将在这里分享。对于当前的实现(v2 测试版),是否为两者都指定 AsReference=true 很重要,无论哪个对象都指定给 Serializer.Serialize()。

public class Program

    using System.IO;
    using ProtoBuf;
    using System;

    public static void main();
    
        AnOwner owner1, owner2;
        AnOwner owner = new AnOwner();
        SomeData data = new SomeData();
        owner.Data = data;
        data.Owner = owner;
        string file = "sandbox.txt";

        try  File.Delete(file);  catch ; // Just in case, cos' it felt like some caching was in place.

        using (var fs = File.OpenWrite(file))  Serializer.Serialize(fs, owner); 
        using (var fs = File.OpenRead(file))  owner1 = Serializer.Deserialize<AnOwner>(fs); 
        using (var fs = File.OpenRead(file))  owner2 = Serializer.Deserialize<AnOwner>(fs); 

        Console.WriteLine("SomeData.i: 0, 1, 2, 3", owner1.Data.i, owner1.Data.Owner.Data.i, owner2.Data.i, owner2.Data.Owner.Data.i);
        Console.WriteLine("AnOwner.i: 0, 1, 2, 3", owner1.i, owner1.Data.Owner.i, owner2.i, owner2.Data.Owner.i);

        System.Diagnostics.Debug.Assert(owner1 == owner1.Data.Owner, "1. Expect reference same, but not the case.");
        System.Diagnostics.Debug.Assert(owner2 == owner2.Data.Owner, "2. Expect reference same, but not the case.");
        System.Diagnostics.Debug.Assert(owner1 != owner2, "3. Expect reference different, but not the case.");
    


[ProtoContract()]
public class SomeData

    public static readonly Random RAND = new Random(2);

    [ProtoMember(1, AsReference = true)]
    public AnOwner Owner;

    // Prove that SomeData is only instantiated once per deserialise
    public int i = RAND.Next(100);

    public SomeData()  


[ProtoContract()]
public class AnOwner

    public static readonly Random RAND = new Random(3);

    [ProtoMember(1, AsReference=true)]
    public SomeData Data;

    // Prove that AnOwner is only instantiated once per deserialise
    public int i = RAND.Next(100);

    /// <summary>
    /// ProtoBuf deserialization constructor
    /// </summary>
    public AnOwner()  

【问题讨论】:

实际上,在回答这个问题时,你让我成为了一个边缘案例,我可以改进当前处理中的内容(不影响格式/兼容性等) - 所以感谢您提出这个问题! @Marc o_O 感谢您的评论。虽然我实际上并没有做太多。我仍在试图理解为什么它对我不起作用。感觉它的步骤可能类似于 C# 初始化静态类变量的方式......只是一个想法。 我要离开几天,但我会在回来时阅读示例 - 希望能解释任何问题。 【参考方案1】:

基本上,它不是直接序列化 AnOwner,而是序列化具有以下一项或多项的假(实际上并不存在)对象:

已经看到的对象的现有键(整数) 新密钥 对象本身 类型信息(如果 DynamicType 已启用)

当序列化一个被跟踪的对象时,会检查一个内部列表;如果对象在那里(以前见过),则(仅)写入旧密钥。否则,将针对该对象生成并存储一个新密钥,然后写入新密钥和对象。反序列化时,如果找到“新键”,则反序列化对象数据,并根据该键存储新对象(实际上这里的顺序有点复杂,要处理递归)。如果找到“旧键”,则使用内部列表获取旧的旧对象。

对于几乎所有对象,比较都是基于引用相等(即使相等被覆盖)。请注意,这对字符串的工作方式略有不同,它们比较 string 是否相等 - 因此字符串 "Fred" 的两个不同实例仍将共享一个键。

我相信大多数递归方案都受支持,但如果您遇到问题,请告诉我。

【讨论】:

以上是关于使用递归引用理解 ProtoBuf-Net AsReference的主要内容,如果未能解决你的问题,请参考以下文章

Protobuf-net“反序列化期间引用跟踪对象更改引用”错误(2)

Protobuf-net 对象图引用完整性

如何在 xamarin ios pcl 中使用 protobuf-net

javascript --- 递归的简单理解

PHP实现递归的三种方法

如何使用 RuntimeTypeModel 将 ProtoInclude 与 protobuf-net 中的类型相关联?