【问题标题】:Using ProtoBuf-net to deseriailize a derived type (dictionary) does not properly set object fields使用 ProtoBuf-net 反序列化派生类型(字典)未正确设置对象字段
【发布时间】:2014-10-08 16:43:50
【问题描述】:

我正在尝试序列化然后反序列化其类派生自带有字符串成员字段的Dictionary<string,int> 的对象。

    public class TempClass : Dictionary<string, int>
    {
        public string Version;

        public TempClass() { }
    }

我编写了一个单元测试来捕获我遇到的问题:使用 protobuf-net 序列化或反序列化(到/从字节 [])时未设置成员字段。当验证反序列化的Version 设置正确时,此测试在最终的Assert 中失败。它始终设置为null,而不是正确的"someVersion"

    [TestClass]
    public class serializationTest
    {
        [TestMethod]
        public void TestMethod1()
        {
            string newVersion = "someVersion";

            TempClass original = new TempClass()
            {
                {"a", 2},
                {"b", 3},
                {"c", 1},
            };
            original.Version = newVersion;

            byte[] serialized = Serialize(original);

            TempClass deserialized = Deserialize(serialized);

            // Validate
            foreach (var pair in original)
            {
                Assert.IsTrue(deserialized.ContainsKey(pair.Key));
                Assert.AreEqual(pair.Value, deserialized[pair.Key]);
            }

            Assert.AreEqual(newVersion, original.Version, "original mapping version not set correctly");
            Assert.AreEqual(newVersion, deserialized.Version, "deserialized version doesn't match");
        }

        private static TempClass Deserialize(byte[] serialized)
        {
            TempClass deserialized;
            using (MemoryStream ms = new MemoryStream())
            {
                ms.Write(serialized, 0, serialized.Length);
                ms.Position = 0;
                deserialized = Serializer.Deserialize<TempClass>(ms);
            }
            return deserialized;
        }

        private static byte[] Serialize(TempClass mapping)
        {
            byte[] serialized;
            using (MemoryStream ms = new MemoryStream())
            {
                Serializer.Serialize(ms, mapping);
                serialized = ms.ToArray();
            }
            return serialized;
        }
    }

我已经尝试使用BinaryFormatterDataContractSerializer 进行相同的工作,但无济于事。有人可以帮我找出导致此测试失败的错误之处吗?

后续问题:如果我像这样重新定义TempClass,则始终调用构造函数,而不是将成员字段正确设置为原始Version。如何在构造函数不创建新的Version 的情况下进行反序列化,而只复制原始的?

    public class TempClass : Dictionary<string, int>
    {
        public string Version;

        public TempClass() 
        { 
            Version = DateTime.UtcNow.ToString("s");
        }
    }

【问题讨论】:

  • 感谢您提供简单易用的测试。这样可以节省时间。

标签: c# serialization protobuf-net deep-copy


【解决方案1】:

这个效果可能与protobuf中IDictionary类型序列化的内部实现有关。 您可以在字典中添加版本数据或像此示例一样重写您的 dto 对象。 如果您使用此数据对象,它将修复您的测试:

[ProtoContract]
public class TempClass 
{
    [ProtoMember(1)]
    public Dictionary<string, int> data;

    [ProtoMember(2)]
    public string Version;

    public TempClass() { }
}

第三种方式是自己写序列化。

【讨论】:

  • 如果TempClass 不必也是IEnumerableIDictionary 等,这将起作用。但是,更改TempClass 的类型会破坏很多现有代码,所以除非我也实现Dictionary 的所有接口,否则此解决方案将不起作用。
  • 您不需要实现接口,您可以在序列化之前将数据包装到 dto 对象。
  • 事实仍然是,如果我在单元测试中使用您的类版本,那么代码将无法编译。我需要编译 AND 的代码通过测试。
【解决方案2】:
[ProtoContract]
public class TempClass 
{
    [ProtoMember(1)]
    public Dictionary<string, int> data;

    [ProtoMember(2)]
    public string Version;

    public TempClass() { }
}

[TestClass]
public class serializationTest
{
    [TestMethod]
    public void TestMethod1()
    {
        string newVersion = "someVersion";

        TempClass original = new TempClass()
        {
            data = new Dictionary<string,int>
            {
                {"a", 2},
                {"b", 3},
                {"c", 1},
            },
            Version = newVersion
        };

        byte[] serialized = Serialize(original);

        TempClass deserialized = Deserialize(serialized);

        // Validate
        foreach (var pair in original.data)
        {
            Assert.IsTrue(deserialized.data.ContainsKey(pair.Key));
            Assert.AreEqual(pair.Value, deserialized.data[pair.Key]);
        }

        Assert.AreEqual(newVersion, original.Version, "original mapping version not set correctly");
        Assert.AreEqual(newVersion, deserialized.Version, "deserialized version doesn't match");
    }

    private static TempClass Deserialize(byte[] serialized)
    {
        TempClass deserialized;
        using (MemoryStream ms = new MemoryStream())
        {
            ms.Write(serialized, 0, serialized.Length);
            ms.Position = 0;
            deserialized = Serializer.Deserialize<TempClass>(ms);
        }
        return deserialized;
    }

    private static byte[] Serialize(TempClass mapping)
    {
        byte[] serialized;
        using (MemoryStream ms = new MemoryStream())
        {
            Serializer.Serialize(ms, mapping);
            serialized = ms.ToArray();
        }
        return serialized;
    }
}

【讨论】:

  • 您已修改测试以通过新的实现。这违反了最初编写单元测试以强制执行的合同。我正在寻找原始问题的答案。不是您自己问题的答案。
  • 哦..太好了。你要求解决方案 - 我给你一个方法。你要代码——我给你一个代码。比你投反对票。所以,我认为这对某些人会有帮助。
【解决方案3】:

使用[ProtoContract(UseProtoMembersOnly = true, IgnoreListHandling = true)] 而不是[ProtoContract] 为我解决了这个问题。

这将阻止 protobuf-net 使用字典规则序列化类。 (见this

这是一个例子。

[ProtoContract(UseProtoMembersOnly = true, IgnoreListHandling = true)]
public class ProtobufTest<T, P> : IDictionary<T, P>
{
    [ProtoMember(1)]
    private readonly Dictionary<T, P> _dataset;

    [ProtoMember(2)]
    public string Name;

    private ProtobufTest(Dictionary<T, P> dataset, string name)
    {
        _dataset = dataset ?? new Dictionary<T, P>();
        Name = name;
    }
    public ProtobufTest(string name) : this(new Dictionary<T, P>(), name) { }
    private ProtobufTest() : this(null, string.Empty) {}

    //
    // IDictionary implementation is omitted.
    //
}

这是一个示例单元测试。

    [Test]
    public void ProtobufTestNameSerializeDeserialize()
    {
        ProtobufTest<double, double> t = new ProtobufTest<double, double>("233");
        ProtobufTest<double, double> d;
        using (MemoryStream ms = new MemoryStream())
        {
            Serializer.SerializeWithLengthPrefix(ms, t, PrefixStyle.Base128);
            ms.Position = 0;
            d = Serializer.DeserializeWithLengthPrefix<ProtobufTest<double, double>>(ms, PrefixStyle.Base128);
        }
        Assert.AreEqual(t.Name, d.Name);
    }

(这些代码仅供参考)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2010-10-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多