Понимание ProtoBuf-Net AsReference с рекурсивными ссылками - PullRequest
4 голосов
/ 25 августа 2011

Я пробую опцию AsReference в ProtoMember для рекурсивной ссылки.Если я создаю 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() { }
}

1 Ответ

4 голосов
/ 25 августа 2011

По сути, вместо сериализации AnOwner напрямую , он сериализует поддельный (на самом деле не существует) объект с одним или несколькими из следующих элементов:

  • существующий ключ (целое число) к объекту, который уже был замечен
  • новый ключ
  • сам объект
  • информация о типе (если DynamicType включен)

При сериализации отслеживаемого объекта проверяется внутренний список;если объект существует (был замечен ранее), тогда пишется старый ключ (только).В противном случае новый ключ генерируется и сохраняется для этого объекта, а новый ключ и объект записываются.При десериализации, если обнаружен «новый ключ», данные объекта десериализуются, и новый объект сохраняется в этом ключе (фактически порядок здесь немного сложен для обработки рекурсии).Если «старый ключ» найден, внутренний список используется для извлечения старого старого объекта.

Для почти всех объектов сравнение выполняется на основе ссылочного равенства (даже если равенство переопределено).Обратите внимание, что это работает немного по-разному для строк, которые сравниваются для равенства string - поэтому два разных экземпляра строки "Fred" все равно будут иметь общий ключ.

Я считаю, что большинство сценариев рекурсииподдерживается, но если у вас возникли проблемы, пожалуйста, дайте мне знать.

...