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,因为它们是不同的类型,而且我们没有在任何地方说过它Part应该被引用跟踪,所以我们没有理由期望它通过。请注意,这Whole 跟踪的,因此以下通过:

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

如果我们想引用-跟踪Part,我们需要告诉它(引用-跟踪不是默认的);幸运的是,应用于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 回答