You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.30 02:06:03