1

R556を使用すると、次の複雑なシナリオの参照追跡が失敗します。テストのアサーションを参照してください。カスタムコレクションのサロゲートの代わりにshimクラスを使用しても、問題は変わりません。

どうやらSOは私の説明が気に入らないので、この役に立たないテキストによって、私の質問がロボットと一緒に集まることができるかもしれません。

using System.Collections.Generic;
using System.IO;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using ProtoBuf;
using ProtoBuf.Meta;

[TestClass]
public class UnitTest
{
    [ProtoContract]
    public class Whole
    {
        public Whole() { this.Parts = new PartCollection { Whole = this }; }
        [ProtoMember(1)]
        public readonly PartCollection Parts;
    }

    [ProtoContract]
    public class Part
    {
        [ProtoMember(1, AsReference = true)]
        public Whole Whole { get; set; }
    }

    public class PartCollection : List<Part>
    {
        public Whole Whole { get; set; }
    }

    [ProtoContract]
    public class Assemblage
    {
        [ProtoMember(1)]
        public readonly PartCollection Parts = new PartCollection();
    }

    [ProtoContract]
    public class PartCollectionSurrogate
    {
        [ProtoMember(1)]
        private PartCollection Collection { get; set; }

        [ProtoMember(2)]
        private Whole Whole { get; set; }

        public static implicit operator PartCollectionSurrogate(PartCollection value)
        {
            if (value == null) return null;
            return new PartCollectionSurrogate { Collection = value, Whole = value.Whole };
        }

        public static implicit operator PartCollection(PartCollectionSurrogate value)
        {
            if (value == null) return new PartCollection();
            value.Collection.Whole = value.Whole;
            return value.Collection;
        }
    }

    [TestMethod]
    public void TestMethod1()
    {
        RuntimeTypeModel.Default.Add(typeof(PartCollection), false).SetSurrogate(typeof(PartCollectionSurrogate));
        using (var stream = new MemoryStream())
        {
            {
                var whole = new Whole();
                var part = new Part { Whole = whole };
                whole.Parts.Add(part);
                var assemblage = new Assemblage();
                assemblage.Parts.Add(part);
                Serializer.Serialize(stream, assemblage);
            }

            stream.Position = 0;

            var obj = Serializer.Deserialize<Assemblage>(stream);
            {
                var assemblage = obj;
                var whole = assemblage.Parts[0].Whole;
                var referenceEqual = ReferenceEquals(assemblage.Parts[0], whole.Parts[0]);

                // The following assertion fails.
                Assert.IsTrue(referenceEqual);
            }
        }
    }
}

これは、機能するサロゲートの代わりにshimクラスを使用して修正されたコードです。

using System.Collections.Generic;
using System.IO;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using ProtoBuf;

[TestClass]
public class UnitTest2
{
    [ProtoContract]
    public class Whole
    {
        public Whole() { this.Parts = new PartCollection { Whole = this }; }

        public PartCollection Parts;

        [ProtoMember(1)]
        public PartCollectionData PartsData
        {
            get { return PartCollectionData.ToData(Parts); }
            set { Parts = PartCollectionData.FromData(value); }
        }
    }

    [ProtoContract]
    public class Part
    {
        [ProtoMember(1, AsReference = true)]
        public Whole Whole { get; set; }
    }

    [ProtoContract(IgnoreListHandling = true)]
    public class PartCollection : List<Part>
    {
        public Whole Whole { get; set; }
    }

    [ProtoContract]
    public class Assemblage
    {
        public PartCollection Parts = new PartCollection();

        [ProtoMember(1)]
        public PartCollectionData PartsData
        {
            get { return PartCollectionData.ToData(Parts); }
            set { Parts = PartCollectionData.FromData(value); }
        }
    }

    [ProtoContract]
    public class PartCollectionData
    {
        [ProtoMember(1, AsReference = true)]
        public List<Part> Collection { get; set; }

        [ProtoMember(2, AsReference = true)]
        public Whole Whole { get; set; }

        public static PartCollectionData ToData(PartCollection value)
        {
            if (value == null) return null;
            return new PartCollectionData { Collection = value, Whole = value.Whole };
        }

        public static PartCollection FromData(PartCollectionData value)
        {
            if (value == null) return null;

            PartCollection result = new PartCollection { Whole = value.Whole };
            if (value.Collection != null)
                result.AddRange(value.Collection);
            return result;
        }
    }

    [TestMethod]
    public void TestMethod1()
    {
        using (var stream = new MemoryStream())
        {
            {
                var whole = new Whole();
                var part = new Part { Whole = whole };
                whole.Parts.Add(part);
                var assemblage = new Assemblage();
                assemblage.Parts.Add(part);
                Serializer.Serialize(stream, assemblage);
            }

            stream.Position = 0;

            var obj = Serializer.Deserialize<Assemblage>(stream);
            {
                var assemblage = obj;
                var whole = assemblage.Parts[0].Whole;
                Assert.AreSame(assemblage.Parts[0].Whole, whole.Parts[0].Whole, "Whole");
                Assert.AreSame(assemblage.Parts[0], whole.Parts[0], "Part");
            }
        }
    }
}

これが機能する修正されたサロゲートコードです。

using System.Collections.Generic;
using System.IO;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using ProtoBuf;
using ProtoBuf.Meta;

[TestClass]
public class UnitTest
{
    [ProtoContract]
    public class Whole
    {
        public Whole() { this.Parts = new PartCollection { Whole = this }; }

        [ProtoMember(1)]
        public PartCollection Parts;
    }

    [ProtoContract]
    public class Part
    {
        [ProtoMember(1, AsReference = true)]
        public Whole Whole { get; set; }
    }

    [ProtoContract(IgnoreListHandling = true)]
    public class PartCollection : List<Part>
    {
        public Whole Whole { get; set; }
    }

    [ProtoContract]
    public class Assemblage
    {
        [ProtoMember(1)]
        public PartCollection Parts = new PartCollection();
    }

    [ProtoContract]
    public class PartCollectionSurrogate
    {
        [ProtoMember(1, AsReference = true)]
        public List<Part> Collection { get; set; }

        [ProtoMember(2, AsReference = true)]
        public Whole Whole { get; set; }

        public static implicit operator PartCollectionSurrogate(PartCollection value)
        {
            if (value == null) return null;
            return new PartCollectionSurrogate { Collection = value, Whole = value.Whole };
        }

        public static implicit operator PartCollection(PartCollectionSurrogate value)
        {
            if (value == null) return null;
            PartCollection result = new PartCollection { Whole = value.Whole };
            if (value.Collection != null)
                result.AddRange(value.Collection);
            return result;
        }
    }

    [TestMethod]
    public void TestMethod1()
    {
        RuntimeTypeModel.Default.Add(typeof(PartCollection), true).SetSurrogate(typeof(PartCollectionSurrogate));
        using (var stream = new MemoryStream())
        {
            {
                var whole = new Whole();
                var part = new Part { Whole = whole };
                whole.Parts.Add(part);
                var assemblage = new Assemblage();
                assemblage.Parts.Add(part);
                Serializer.Serialize(stream, assemblage);
            }

            stream.Position = 0;

            var obj = Serializer.Deserialize<Assemblage>(stream);
            {
                var assemblage = obj;
                var whole = assemblage.Parts[0].Whole;
                Assert.AreSame(assemblage.Parts[0].Whole, whole.Parts[0].Whole, "Whole");
                Assert.AreSame(assemblage.Parts[0], whole.Parts[0], "Part"); 
            }
        }
    }
}
4

1 に答える 1

1

わかった; ここでは複数のことが起こっています。

最初に注意すべきことは、サロゲートが現在使用されていないことです。リストが優先されます。次の方法で調整できます。

[ProtoContract(IgnoreListHandling = true)]
public class PartCollection : List<Part>
{
    public Whole Whole { get; set; }
}

(ただし、この特定のケースでは、内部の微調整 (r558) も必要であることに注意してください。これは、おそらく、何か危険なことをしているという即時の警告になるはずです)

これを行うと、例外が発生します。

テスト 'Examples.Issues.SO11705351.TestMethod1' が失敗しました: ProtoBuf.ProtoException : 再帰の可能性が検出されました (オフセット: 1 レベル): Examples.Issues.SO11705351+PartCollection

これは完全に正しいです。あなたのサロゲートにPartCollectionは、それが表現しようとしている正確なものが含まれています。これは明らかな無限ループです。それを修正しましょう - また、あなたが参照追跡をしたいように見えるという事実を修正しますWholeが、サロゲートでそれを指定していません:

[ProtoContract]
public class PartCollectionSurrogate
{
    [ProtoMember(1)]
    private List<Part> Collection { get; set; }

    [ProtoMember(2, AsReference = true)]
    private Whole Whole { get; set; }

    public static implicit operator PartCollectionSurrogate(PartCollection value)
    {
        if (value == null) return null;
        return new PartCollectionSurrogate { Collection = value, Whole = value.Whole };
    }

    public static implicit operator PartCollection(PartCollectionSurrogate value)
    {
        if (value == null) return null;

        PartCollection result = new PartCollection {Whole = value.Whole};
        if(value.Collection != null)
        { // add the data we colated
            result.AddRange(value.Collection);
        }
        return result;
    }
}

わかった; そのため、適切なデータのようなものをシリアル化しています。

単体テストがまだ合格していないことに気付きました。と比較assemblage.Parts[0]して いwhole.Parts[0]ます。assemblageこれで、は と同じインスタンスではないことがわかりますwhole。これらは型が異なるためです。また、Parts を参照追跡する必要があるとはどこにも述べていません。Whole 追跡されることに注意してください。したがって、次のパスが渡されます。

Assert.AreSame(assemblage.Parts[0].Whole, whole.Parts[0].Whole, "Whole");

reference-track が必要な場合はPartそれを伝える必要があります(reference-tracking はデフォルトではありません)。幸いなことに、リストAsReferenceに適用するということは、 「リストを参照追跡する」ではなく、「アイテムを参照追跡する」ことを意味するため、サロゲートをもう少し微調整します。

        [ProtoMember(1, AsReference = true)]
        private List<Part> Collection { get; set; }

そして今、これらは両方とも合格します:

Assert.AreSame(assemblage.Parts[0].Whole, whole.Parts[0].Whole, "Whole");
Assert.AreSame(assemblage.Parts[0], whole.Parts[0], "Part");

でも!!!!!!

私が言わなければならないのは、これは微妙で複雑になってきているということです。それが起こり始めるときはいつでも、私は常にアドバイスします: より単純な DTO モデルをシリアライズすることを検討し、必要に応じて複雑なドメイン モデルとの間でマッピングするだけです。

于 2012-07-30T08:01:08.080 に答える