使用 ProtoBuf-net 反序列化派生类型(字典)未正确设置对象字段

Posted

技术标签:

【中文标题】使用 ProtoBuf-net 反序列化派生类型(字典)未正确设置对象字段【英文标题】:Using ProtoBuf-net to deseriailize a derived type (dictionary) does not properly set object fields 【发布时间】: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");
        
    

【问题讨论】:

感谢您提供简单易用的测试。这样可以节省时间。 【参考方案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);
    

(这些代码仅供参考)

【讨论】:

以上是关于使用 ProtoBuf-net 反序列化派生类型(字典)未正确设置对象字段的主要内容,如果未能解决你的问题,请参考以下文章

使用 protobuf-net 反序列化 int& 类型

使用 protobuf-net 反序列化当前流位置的类型

使用知道架构的 protobuf-net 反序列化未知对象

需要在运行时确定哪些类型 protobuf-net 可以序列化/反序列化

在 protobuf-net 中,有没有办法指定在序列化/反序列化给定类型时要使用的自定义方法?

如何使用 protobuf-net 序列化/反序列化锯齿状/嵌套数组?