【问题标题】:ProtoBuf Net Dictionary Duplicate Keys Handling (Map)ProtoBuf 网络字典重复键处理(映射)
【发布时间】:2020-04-30 21:44:23
【问题描述】:

我有一些用 protobuf.net 序列化的数据。数据是一个地图,并且包含一些重复项(这是因为我的密钥没有实现 IEquatable)

我想将数据反序列化为字典并忽略重复项。 似乎有一个属性,即[ProtoMap(DisableMap=false)],文档说:

禁用“地图”处理;字典将使用 .Add(key, value) 代替 [键] = 值。 ...

基本上我希望行为是[key] = value,但显然该属性被忽略了。

我做错了吗?有什么方法可以实现忽略重复项的期望(和记录)行为?

示例代码: 1. 产生重复数据:

            // ------------- ------------- ------------- ------------- ------------- ------------- -------------
            //  The following part generated the bytes, which requires the key NOT to implement IEquatable
            // ------------- ------------- ------------- ------------- ------------- ------------- -------------

            var cache = new MyTestClass() { Dictionary = new Dictionary<MyTestKey, string>() };

            cache.Dictionary[new MyTestKey { Value = "X" }] = "A";
            cache.Dictionary[new MyTestKey { Value = "X" }] = "B";

            var bytes = cache.Serialize();

            var bytesStr = string.Join(",", bytes); // "10,8,10,3,10,1,88,18,1,65,10,8,10,3,10,1,88,18,1,66";

    //..



    [DataContract]
    public class MyTestKey
    {
        [DataMember(Order = 1)]
        public string Value { get; set; }
    }

    [DataContract]
    public class MyTestClass
    {
        [DataMember(Order = 1)]
        [ProtoMap(DisableMap = false)]
        public Dictionary<MyTestKey, string> Dictionary { get; set; }
    }

´´´

2. Try deserialize the data, with property IEquatable, which fails..:

[DataContract]
public class MyTestKey : IEquatable<MyTestKey>
{
    [DataMember(Order = 1)]
    public string Value { get; set; }

    public bool Equals(MyTestKey other)
    {
        if (ReferenceEquals(null, other)) return false;
        if (ReferenceEquals(this, other)) return true;
        return Value == other.Value;
    }

    public override bool Equals(object obj)
    {
        if (ReferenceEquals(null, obj)) return false;
        if (ReferenceEquals(this, obj)) return true;
        if (obj.GetType() != this.GetType()) return false;
        return Equals((MyTestKey) obj);
    }

    public override int GetHashCode()
    {
        return (Value != null ? Value.GetHashCode() : 0);
    }
}
//..
var bytesStr2 = "10,8,10,3,10,1,88,18,1,65,10,8,10,3,10,1,88,18,1,66";
var bytes2 = bytesStr2.Split(',').Select(byte.Parse).ToArray();
var cache = bytes2.DeserializeTo<MyTestClass>(); 

´´´

异常已添加具有相同键的项目。

public static class SerializationExtensions
{
    public static T DeserializeTo<T>(this byte[] bytes)
    {
        if (bytes == null)
            return default(T);

        using (var ms = new MemoryStream(bytes))
        {

            return Serializer.Deserialize<T>(ms);
        }
    }

    public static byte[] Serialize<T>(this T setup)
    {
        using (var ms = new MemoryStream())
        {
            Serializer.Serialize(ms, setup);
            return ms.ToArray();
        }
    }

【问题讨论】:

    标签: c# dictionary duplicates protobuf-net


    【解决方案1】:

    这里发生了一些不同的事情; “地图”模式实际上是您在此处想要的模式 - 所以不是您要禁用地图,而是实际上您是在尝试强制开启(现在在最常见的字典场景中默认开启)。

    有一些并发症:

    1. 库仅在处理[ProtoMember(...)] 的[ProtoContract(...)] 时处理[ProtoMap(...)]
    2. 即便如此,它也只处理 [ProtoMap(...)] 的键类型,这些键类型在原型规范中作为“映射”键是有效的
    3. 您可以手动打开它(不是通过属性),但在 v2.* 中,它会在运行时强制执行与 #2 相同的检查,这意味着它会失败

    v3.* 中 #3 works 的手动启用(目前处于 alpha 阶段):

    RuntimeTypeModel.Default[typeof(MyTestClass)][1].IsMap = true;
    

    然而,这显然是不优雅的,今天需要使用 alpha 版本(我们已经在 Stack Overflow 的生产环境中使用了很长时间;我只需要一起发布- 文档等)。

    鉴于它有效,我很想建议我们应该软化 v3.* 中的 #2,这样虽然 默认 行为保持不变,但它仍会检查 [ProtoMap(...)] 的自定义类型,并启用该模式。我对是否要软化 #1 犹豫不决。

    我很想知道你对这些事情的看法!

    但要确认:以下在 v3.* 中工作正常并输出 "B"(代码的小解释:在 protobuf 中,附加===根对象的合并,因此一个接一个地序列化两个有效负载具有相同效果如同使用组合内容序列化字典,因此两个 Serialize 调用使用两个相同的键欺骗有效负载):

    static class P
    {
        static void Main()
        {
            using var ms = new MemoryStream();
            var key = new MyTestKey { Value = "X" };
            RuntimeTypeModel.Default[typeof(MyTestClass)][1].IsMap = true;
            Serializer.Serialize(ms, new MyTestClass() { Dictionary = 
              new Dictionary<MyTestKey, string> { { key, "A" } } });
            Serializer.Serialize(ms, new MyTestClass() { Dictionary =
              new Dictionary<MyTestKey, string> { { key, "B" } } });
    
    
            ms.Position = 0;
            var val = Serializer.Deserialize<MyTestClass>(ms).Dictionary[key];
            Console.WriteLine(val); // B
        }
    }
    

    我认为我希望喜欢的是,如果在 v3.* 中,它没有 IsMap = true 行,使用:

    [ProtoContract]
    public class MyTestClass
    {
        [ProtoMember(1)]
        [ProtoMap] // explicit enable here, because not a normal map type
        public Dictionary<MyTestKey, string> Dictionary { get; set; }
    }
    

    【讨论】:

    • 软化 2. 会很棒。我不知道使用非默认键类型的 proto 的内部工作是否存在问题,但从 C# 的角度来看,这应该是完全可能的。
    • 我现在已经实现了一个快速的解决方法,将我的数据反序列化为一个键值元组数组而不是字典(这是二进制兼容的),然后自己处理字典的创建
    • @trykyn(第二条评论)确实是完全有效的
    猜你喜欢
    • 2019-12-12
    • 1970-01-01
    • 2021-02-23
    • 2022-09-27
    • 2021-08-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-06-20
    相关资源
    最近更新 更多