1

Banana List には、Fruits List 内のオブジェクトへの参照が含まれています。Fruits List のバナナが次々と配置されます。さらに、昇順の数字があります。(バナナの追加と削除はこれらのプロパティを保持する必要があります) 問題は、デシリアライズ後、Basket の Bananas List と Fruits List に異なるオブジェクトが含まれているように見えることです。それを回避する方法はありますか?

2 番目の質問は、Banana の ContainedBy プロパティに関するものです。Cos Bananas は、特定のコンテナーを指している場所を認識していますが、逆シリアル化の後、神がどこを指しているのか (それらが含まれているバスケット以外のバスケットに固有であるため) を示しています。キャッチはどこですか?

    [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 は失われます。

4

1 に答える 1

2

デフォルトでは、protobuf-net はツリー シリアライザーであり、グラフ シリアライザーではありません。したがって、関連する参照を尊重するように指示するまで、バナナと果物の下に情報を別々にシリアル化します。さらに、ここで説明されているように、二重シリアル化に注意する必要があります。この同じ投稿では、インターフェイスとそのクラスの間の現在の「作業が必要」な領域についても説明しています。これは、ここであなたに影響を与える可能性があります。Fruitsとの順序を入れ替えることで、これを大部分回避したと思いますBananas

リストが空のときに爆発していたという単純な理由で「lastBanana / index」コードを削除したことに注意してください。そこにあなたの意図を先取りしたくなかった-再追加するために残しますそのコード。

ただし、私の主な考えは単純です。メイン ドメイン モデルをシリアル化しているようです。それには本質的に問題はありません、もしそれが難しくなり始めたら、シリアル化のためにより単純な DTO モデルに切り替え、DTO/ドメイン モデルとの間でマッピングすることを常にお勧めします。

とにかく、以下はうまくいくようです:

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
    }
}
于 2012-10-01T06:16:19.380 に答える