如何在Protobuf序列化字典时将结构体作为键类型?
问题解答
1. Protobuf能否对复杂类型的Dictionary进行序列化/反序列化?
可以,但得满足几个必要条件:
- 作为字典Key的复杂类型(这里是
MyStruct结构体)必须符合Protobuf的序列化要求:- 结构体已经标记
[ProtoContract](你提到已添加相关特性,这部分没问题) - 结构体必须正确实现值相等性逻辑——要么重写
Equals()和GetHashCode()方法,要么用C# 9+的record struct。因为字典的Key依赖相等性判断,反序列化后如果相等逻辑失效,会导致Key无法被正常识别,甚至触发空引用异常 - 如果用的是protobuf-net,结构体最好有无参构造函数;如果没有,得通过配置允许序列化库调用非公共构造函数,保证反序列化时能实例化结构体
- 结构体已经标记
2. ExpectedNumbers的特性装饰存在什么问题?
你的代码里同时给ExpectedNumbers加了[MessagePackMember(10)]和[ProtoMember(10)],这是核心问题:
- 这两个特性分属MessagePack和protobuf-net两个完全独立的序列化库,同时使用会让序列化逻辑混乱,无法确定遵循哪一套规则,直接导致反序列化失败
另外还有两个潜在坑需要注意:
TheClass只有带参数的构造函数,protobuf-net默认会找无参构造函数来反序列化。如果没有无参构造函数,必须给当前构造函数标记[ProtoConstructor]特性,否则反序列化时可能无法正确初始化对象,进而导致ExpectedNumbers为null- 虽然属性初始化时写了
= new Dictionary<MyStruct, int>(),但如果序列化库是通过反射直接设置字段而非调用构造函数,这个初始化逻辑可能不会生效,最终字典还是会变成null
修复建议
- 删掉
[MessagePackMember(10)],只保留[ProtoMember(10)](如果用的是protobuf-net);如果用的是MessagePack,就反过来,确保只使用一套序列化库的特性 - 给带参数的构造函数加上
[ProtoConstructor],让protobuf-net明确知道反序列化时要调用这个构造函数:[ProtoConstructor] public TheClass(Guid operationId, Dictionary<MyStruct, int> expectedNumbers) { this.OperationId = operationId; this.ExpectedNumbers = expectedNumbers ?? new Dictionary<MyStruct, int>(); } - 确保
MyStruct正确实现相等性逻辑,示例代码:[ProtoContract] public struct MyStruct : IEquatable<MyStruct> { [ProtoMember(1)] public int Id { get; set; } public bool Equals(MyStruct other) { return Id == other.Id; } public override bool Equals(object obj) { return obj is MyStruct other && Equals(other); } public override int GetHashCode() { return Id.GetHashCode(); } }
内容的提问来源于stack exchange,提问作者yellow_submarine
相关产品推荐
相关产品推荐

