通过递归引用了解ProtoBuf-Net AsReference

时间:2011-08-25 05:40:36

标签: protobuf-net

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

[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 beta),如果为两者指定了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() { }
}

1 个答案:

答案 0 :(得分:4)

基本上,它不是直接序列化AnOwner ,而是使用以下一个或多个序列化伪造(并不存在)对象:

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

序列化跟踪对象时,会检查内部列表;如果对象在那里(之前已经看到),则写入旧密钥(仅)。否则,将为该对象生成并存储新密钥,并写入新密钥和对象。在反序列化时,如果找到“新密钥”,则反序列化对象数据并且针对该密钥存储新对象(实际上这里的顺序有点复杂,以处理递归)。如果找到“旧密钥”,则使用内部列表来获取旧的旧对象。

对于几乎所有对象,比较是基于引用相等性的(即使覆盖了相等性)。请注意,对于字符串,这种情况略有不同,对于字符串进行比较,因此字符串"Fred"的两个不同实例仍将共享密钥。

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