对于复杂场景,参考跟踪失败

时间:2012-07-28 23:01:51

标签: c# .net protobuf-net

使用R556,以下复杂场景的参考跟踪失败,请参阅测试中的断言。 使用填充程序类而不是自定义集合的代理项不会改变问题。

显然,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);
            }
        }
    }
}

以下是使用填充程序类而不是代理程序的固定代码。

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"); 
            }
        }
    }
}

1 个答案:

答案 0 :(得分: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]。现在,我们知道assemblagewhole不是同一个实例,因为它们是不同的类型,我们没有说过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模型,并根据需要在它和你的复杂域模型之间进行映射。