ProtoBuf Net 字典重复键处理(映射)

4
我有一些数据,用protobuf.net进行了序列化。这些数据是一个映射表,并包含一些重复项(由于我的键没有实现IEquatable而发生)。
我想将数据反序列化为字典并忽略重复项。似乎有一个属性可以做到这一点,即[ProtoMap(DisableMap=false)],文档中说:

禁用"map"处理; 字典将使用 .Add(key, value),而不是[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();
        }
    }
1个回答

0

这里有几件事情需要注意;“map”模式实际上是你在这里想要的 - 所以你并不是试图禁用地图,而是实际上你正在尝试强制打开它(在大多数常见的字典场景中,默认情况下已经打开了)。

这里有一些复杂性:

  1. 当处理 [ProtoContract(...)][ProtoMember(...)] 时,该库仅处理 [ProtoMap(...)]
  2. 即使如此,它仅处理在 proto 规范中作为“map”键的键类型的 [ProtoMap(...)]
  3. 你可以手动打开它(而不是通过属性),但在 v2.* 中它会在运行时执行与 #2 相同的检查,这意味着它将失败

#3 中的手动启用在 v3.* 中有效(目前处于 alpha 版本):

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

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

考虑到它可以工作,我倾向于建议在 v3.* 中放宽 #2,这样虽然默认行为保持不变,但仍会检查自定义类型的[ProtoMap(...)]并启用该模式。至于是否放宽 #1,我还没有决定。

我很想听听您对这些事情的想法!

但是要确认:以下代码在 v3.* 中运行良好,并输出"B"(代码的简单解释:在 protobuf 中,append === merge 适用于根对象,因此连续序列化两个有效负载与序列化具有组合内容的字典具有相同的效果,因此两个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; }
}

软化第二个要求会很好。我不知道使用非默认键类型时,Proto 的内部工作是否会出现问题,但从 C# 的角度来看,完全可以做到。 - trykyn
我现在已经实现了一个快速解决方法,通过将我的数据反序列化为键值元组数组(二进制兼容),然后自己处理字典的创建。 - trykyn
@trykyn,那个(第二条评论)是完全有效的,确实。 - Marc Gravell

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接