ProtoBuf-Net,两个列表的反序列化,其中一个列表是另一个列表的一部分

时间:2012-09-30 21:11:08

标签: c# serialization protobuf-net

香蕉列表包含对水果列表中对象的引用。水果列表中的香蕉一个接一个地放置。此外,他们有数字递增。 (添加和删除香蕉应保留这些属性)问题是反序列化后香蕉列表和篮子中的水果列表似乎包含不同的对象。有没有办法避免它?

我的第二个问题是香蕉中的ContainedBy属性。 Cos香蕉知道他们在哪里指向特定的容器,但在反序列化后他们再次指出上帝知道在哪里(特定于其他篮子而不是它们包含在内)。捕获量在哪里?

    [ProtoContract]
    [ProtoInclude(500, typeof(Basket))]
    public interface IContainer
    {
        [ProtoMember(1, OverwriteList = true)]
        public List<Banana> Bananas { get; set; }

        [ProtoMember(2, OverwriteList = true)]
        public List<IFruit> Fruits { get; set; }

    }

    [ProtoContract]
    public class Basket : IContainer
    {
        [ProtoMember(1, OverwriteList = true)]
        public List<Banana> Bananas { get; set; }

        [ProtoMember(2, OverwriteList = true)]
        public List<IFruit> Fruits { get; set; }

        public void AddBanana()
        {
            var number = Bananas.Count;
            var newBanana = new Banana(number) {ContainedBy = this};
            var lastBanana = Bananas[Bananas.Count - 1];
            var index = Fruits.LastIndexOf(lastBanana);

            Friuts.Insert(index + 1, newBanana);
            Bananas.Add(newBanana);
        }

        public void DeleteBanana(Banana banana)
        {
            Bananas.Remove(banana);
            Fruits.Remove(banana);
            var n = 0;
            foreach (var b in Bananas)
            {
                b.Number = n++;
            }
        }
    }

    [ProtoContract]
    [ProtoInclude(600, typeof(Banana))]
    public interface IFruit
    {
        [ProtoMember(1, AsReference = true)]
        IContainer ContainedBy { get; set; }
    }

    [ProtoContract]
    public class Banana : IFruit
    {
        [ProtoMember(1)]
        public int Number { get; set; }
        [ProtoMember(2, AsReference = true)]
        IContainer ContainedBy { get; set; }
    }

更新#1 Marc Gravell给出的答案非常适合上述问题。但我的目标功能是容器字典,它给出了一个例外:“在反序列化过程中,引用跟踪对象更改了引用”。下面列出的更改适用于Marc Gravell的答案。

[ProtoContract]
class RootObject
{
    [ProtoMember(1, OverwriteList = true, AsReference = true)]
    public Dictionary<string, IContainer> Dictionary { get; set; }
}

...

internal static class Program
{
    private static void Main()
    {
        var basketA = new Basket();
        basketA.AddBanana();
        var basketB = new Basket();
        basketB.AddBanana();
        var root = new RootObject {Dictionary = new Dictionary<string, IContainer>()};
        root.Dictionary.Add("A",basketA);
        root.Dictionary.Add("B",basketB);

        RootObject clone;
        using (var file = File.Create("tmp.bin"))
        {
            Serializer.Serialize<RootObject>(file, root);
        }

        using (var file = File.OpenRead("tmp.bin"))
        {
            clone = Serializer.Deserialize<RootObject>(file);
        }

        foreach (var container in clone.Dictionary.Values) //Exception
        {          
        Console.WriteLine(container.Bananas.Count == container.Fruits.Count); //true
        Console.WriteLine(ReferenceEquals(
           container.Bananas[0],
           container.Fruits[0])); // true
        Console.WriteLine(ReferenceEquals(
            container.Fruits[0].ContainedBy,
            container)); // false

        }           
    }
}

显然,将RootObject中的Dictionary标记从[ProtoMember(1, OverwriteList = true, AsReference = true)]更改为[ProtoMember(1, OverwriteList = true)]会导致异常,但引用ContainedBy会丢失。

1 个答案:

答案 0 :(得分:2)

默认情况下,protobuf-net是树序列化器,而不是图形序列化器;所以,除非你告诉它尊重所涉及的参考文献,否则它将在香蕉和水果下单独序列化信息。此外,您需要注意双序列化as described here。同一篇文章还描述了接口及其类之间当前的“需要工作”区域,这可能会影响到你;我想我通过交换FruitsBananas的顺序来解决这个问题。

请注意,我删除了“lastBanana / index”代码的原因很简单,因为当列表为空时它会爆炸,我不想在那里先发制人的意图 - 我会留给你重新添加该代码。

我的主要想法很简单:看起来你正在序列化你的主域模型。这没有什么本质上错误,但是:如果它开始变得棘手,那么我总是建议:切换到更简单的DTO模型进行序列化,并映射到DTO /域模型。 / p>

无论如何,以下似乎有效:

using ProtoBuf;
using System;
using System.Collections.Generic;

[ProtoContract]
class RootObject
{
    [ProtoMember(1, AsReference = true)]
    public IContainer Container { get; set; }
}

[ProtoContract]
[ProtoInclude(500, typeof(Basket))]
public interface IContainer
{
    [ProtoMember(2, OverwriteList = true, AsReference = true)]
    List<Banana> Bananas { get; set; }

    [ProtoMember(1, OverwriteList = true, AsReference = true)]
    List<IFruit> Fruits { get; set; }

}

[ProtoContract]
public class Basket : IContainer
{
    public List<Banana> Bananas { get; set; }

    public List<IFruit> Fruits { get; set; }

    public void AddBanana()
    {
        if (Bananas == null) Bananas = new List<Banana>();
        if (Fruits == null) Fruits = new List<IFruit>();
        var number = Bananas.Count;
        var newBanana = new Banana { Number = number, ContainedBy = this };

        // var lastBanana = Bananas[Bananas.Count - 1];
        // var index = Fruits.LastIndexOf(lastBanana);

        // Fruits.Insert(index + 1, newBanana);

        Fruits.Add(newBanana);
        Bananas.Add(newBanana);
    }

    public void DeleteBanana(Banana banana)
    {
        Bananas.Remove(banana);
        Fruits.Remove(banana);
        var n = 0;
        foreach (Banana b in Bananas)
        {
            b.Number = n++;
        }
    }
}

[ProtoContract]
[ProtoInclude(600, typeof(Banana))]
public interface IFruit
{
    [ProtoMember(1, AsReference = true)]
    IContainer ContainedBy { get; set; }
}

[ProtoContract]
public class Banana : IFruit
{
    [ProtoMember(1)]
    public int Number { get; set; }

    public IContainer ContainedBy { get; set; }
}


static class Program
{
    static void Main()
    {
        var basket = new Basket();
        var root = new RootObject { Container = basket };
        basket.AddBanana();

        var clone = Serializer.DeepClone(root);
        Console.WriteLine(clone.Container.Fruits.Count == 1); // true
        Console.WriteLine(clone.Container.Bananas.Count == 1); // true
        Console.WriteLine(ReferenceEquals(
            clone.Container.Bananas[0],
            clone.Container.Fruits[0])); // true
        Console.WriteLine(ReferenceEquals(
            clone.Container.Fruits[0].ContainedBy,
            clone.Container)); // true
    }
}