Protobuf-net中oneof的使用方法 如何实现多类型单字段序列化需求
Protobuf-net 实现oneof功能解决方案
一、类定义方式
首先明确:[ProtoInclude]适用于类继承场景,你的多类型互斥需求直接用protobuf-net v2.3.0+内置的oneof特性实现即可,和Google.Protobuf的oneof效果完全一致。
首先定义你的8种无关联消息类型,每个都添加[ProtoContract]特性:
// 示例3种类型,剩余5种按相同规则定义即可 [ProtoContract] public class TextMessage { [ProtoMember(1)] public string Content { get; set; } } [ProtoContract] public class ProgressValueMessage { [ProtoMember(1)] public double Percentage { get; set; } } [ProtoContract] public class ErrorMessage { [ProtoMember(1)] public int Code { get; set; } [ProtoMember(2)] public string Info { get; set; } }
方式1:完全对齐Google.Protobuf用法(带Case枚举)
这种方式和你使用Google.Protobuf的体验完全一致,可通过枚举直接判断类型:
[ProtoContract] public class ProgressUIMessage { [ProtoMember(1)] public int Id { get; set; } // 定义oneof对应的case枚举,枚举值和对应字段的tag保持一致即可 public enum MessageOneofCase { None = 0, Text = 2, ProgressValue = 3, Error = 4 // 剩余5种类型依次分配tag 5~9,枚举值对应设为相同数值即可 } // 关联oneof和枚举属性 [ProtoOneOf(nameof(MessageCase))] public MessageOneofCase MessageCase { get; private set; } // 每个oneof可选类型单独定义属性,分配唯一tag [ProtoMember(2)] public TextMessage Text { get => MessageCase == MessageOneofCase.Text ? (TextMessage)Message : null; set { Message = value; MessageCase = value == null ? MessageOneofCase.None : MessageOneofCase.Text; } } [ProtoMember(3)] public ProgressValueMessage ProgressValue { get => MessageCase == MessageOneofCase.ProgressValue ? (ProgressValueMessage)Message : null; set { Message = value; MessageCase = value == null ? MessageOneofCase.None : MessageOneofCase.ProgressValue; } } [ProtoMember(4)] public ErrorMessage Error { get => MessageCase == MessageOneofCase.Error ? (ErrorMessage)Message : null; set { Message = value; MessageCase = value == null ? MessageOneofCase.None : MessageOneofCase.Error; } } // 可选保留统一存取的object属性,不需要可删除 public object Message { get; private set; } }
方式2:简洁写法
不需要显式定义枚举,代码更精简:
[ProtoContract] public class ProgressUIMessage { [ProtoMember(1)] public int Id { get; set; } // 同组oneof字段的OneofName参数设为相同值即可 [ProtoMember(2, OneofName = "Message")] public TextMessage Text { get; set; } [ProtoMember(3, OneofName = "Message")] public ProgressValueMessage ProgressValue { get; set; } [ProtoMember(4, OneofName = "Message")] public ErrorMessage Error { get; set; } // 剩余5种类型按相同规则添加,分配唯一tag、OneofName统一为Message即可 }
二、识别实际类型的方法
对应方式1(带Case枚举)
和Google.Protobuf用法完全一致,直接判断枚举值即可:
var msg = Serializer.Deserialize<ProgressUIMessage>(stream); switch (msg.MessageCase) { case ProgressUIMessage.MessageOneofCase.Text: // 处理TextMessage类型 break; case ProgressUIMessage.MessageOneofCase.ProgressValue: // 处理ProgressValueMessage类型 break; case ProgressUIMessage.MessageOneofCase.Error: // 处理ErrorMessage类型 break; case ProgressUIMessage.MessageOneofCase.None: // 消息内容为空的处理逻辑 break; }
对应方式2(简洁写法)
判断对应属性是否为null即可,protobuf-net反序列化时只会给oneof中匹配的属性赋值,其他属性均为null:
var msg = Serializer.Deserialize<ProgressUIMessage>(stream); if (msg.Text != null) { // 处理TextMessage类型 } else if (msg.ProgressValue != null) { // 处理ProgressValueMessage类型 } else if (msg.Error != null) { // 处理ErrorMessage类型 }
注意事项
- 同一个oneof分组下的所有字段的tag不可重复,也不能和类内其他非oneof字段的tag冲突
- 序列化时如果同时给oneof分组下的多个属性赋值,protobuf-net只会保留最后赋值的属性,符合protobuf官方oneof语义
- 后续新增类型只需分配未使用的tag,加入oneof分组即可,完全向下兼容
内容的提问来源于stack exchange,提问作者cogumel0
相关产品推荐
相关产品推荐

