协议缓冲和枚举的组合?

13

这是我的原型文件:

message MSG {

  required MsgCodes MsgCode = 1;
  optional int64 Serial = 2;        // Unique ID number for this person.
  required int32 From = 3;  
  required int32 To = 4;  
  //bla bla...
        enum MsgCodes
        {
            MSG = 1;
            FILE = 2;
            APPROVE=4;
            ACK=8;
            ERROR_SENDING=16;
            WORLD=32;
        }
}

在我的C#代码中,我正在尝试:

 msg = msg.ToBuilder().SetMsgCode(msg.MsgCode | MSG.Types.MsgCodes.ACK | MSG.Types.MsgCodes.APPROVE).Build();
 SendToJava(msg);

但是JAVA告诉我:缺少MsgCode(这是一个必需的

删除组合-确实可以解决它

但我需要指定组合

问题

我该怎么解决?

注:

奇怪的是,如果我创建一个msg并设置多个枚举,然后再在C#中读取它,它就可以正常工作... :-(

5个回答

16
在Protobufs中,枚举类型的字段只允许具有枚举中指定的确切数值之一。也就是说,您不能将枚举类型的字段用作位域。如果您需要位域,则需要使用像int32这样的整数类型。即使在具有数字枚举类型(如C ++)的语言中,如果从线路读取的枚举类型的protobuf字段具有无效值,它也将被视为未知字段,因此隐藏起来。
如果您转换为整数,则当然现在有了如何声明标志值的问题。不幸的是,Protobufs没有提供定义常量的好方法。正如您在自己的答案中建议的那样,您可以使用虚拟枚举定义作为hack,但请注意,这种数字值不一定适用于所有语言。在C++和Python中,它们使用数字枚举,因此有效;在Java中,Protobuf枚举具有.getNumber()方法,您可以使用该方法获取数字值;否则,普通的Java枚举不是数字。

(顺便说一下:我是谷歌开源 Protobuf 代码的大部分作者。我也是 Cap'n Proto 的作者,这是一个新的非谷歌项目,旨在取代 Protobufs。除其他优点外,Cap'n Proto 支持在模式文件中定义常量。但截至本文撰写时,C# 支持尚未准备就绪(但正在开发中!)。)


Proto 3 的官方文档(https://developers.google.com/protocol-buffers/docs/proto3#enum)指出:“在反序列化期间,未识别的枚举值将保留在消息中...如果消息被序列化,则未识别的值仍将与消息一起序列化。” - Jason Doucette
@JasonDoucette,没错。未知的枚举值和未知的字段标签都会被同样处理。在这两种情况下,该值将被保存到消息的UnknownFieldSet中。如果再次对该消息进行序列化,则会将未知字段合并回输出中。 - Kenton Varda

8
如果你不需要追求极致的效率(提示:你可能不需要),那么只需使用一个枚举值数组即可。
message Msg {
    // ...
    enum Code
    {
        CODE_UNSPECIFIED = 0;
        CODE_MSG = 1;
        CODE_FILE = 2;
        CODE_APPROVE = 3;
        CODE_ACK = 4;
        CODE_ERROR_SENDING = 5;
        CODE_WORLD = 6;
    }
    repeated Code codes = 5;
}

官方的protobuf文档建议您保留一个等于0的枚举条目,表示类似“未知”的意思。它真正针对的是用作非重复值的枚举(因为在proto3中,0枚举值和未设置之间没有区别),但对所有枚举都值得遵循,以及前缀枚举值与枚举名称的约定,如上所述。

7
我找到了一个解决方案(某种程度上)
需要一个int类型的容器。
message Foo {
  enum Flags {
    FLAG1 = 0x01;
    FLAG2 = 0x02;
    FLAG3 = 0x04;
  }

  // Bitwise-OR of Flags.
  optional uint32 flags = 1;
  • 嗯,这是唯一的解决方案吗?

6
你可以使用消息替代枚举,并使用布尔类型标志所需的标志。
这里是一个简单的闹钟模式示例,可以设置为一周中的多个日期:
message Alarm {
    uint32 hour = 1;
    uint32 minute = 2;
    bool repeat = 3;
    DaysOfWeek daysOfWeek = 4;
    message DaysOfWeek {
        bool sunday = 1;
        bool monday = 2;
        bool tuesday = 3;
        bool wednesday = 4;
        bool thursday = 5;
        bool friday = 6;
        bool saturday = 7;
    }
}

2
这样做效率相当低下,因为每个字段都会有一个标签ID。 - thegreendroid
@thegreendroid 你确定吗?我认为在二进制形式下编码时它是无位置和标签的。 - Louis CAD
1
根据 https://developers.google.com/protocol-buffers/docs/encoding,我很确定 bool 类型具有编码的“wire”类型。 - thegreendroid

1

将该字段定义为整数:

required int32 MsgCode = 1;

将枚举定义如您的问题中一样,即使.proto文件中没有任何引用它的内容。
在您的代码中使用枚举字段。在C#中,就像您的示例一样(尽管这取决于您使用哪个库,例如protobuf-net非常好,并具有轻量级的Enum.Field语法)。在Java中,使用带有_VALUE后缀的字段,例如MsgCodes.APPROVE_VALUE

这不是与 Royi 的自问自答几乎一年前发布的内容相同吗? - Arthur Tacca
@ArthurTacca 他的代码示例展示了相同的一般做法。他的枚举嵌套在消息中,而我的示例(未限定的MsgCodes)则位于相同级别。他的答案几乎完全是代码示例;我的描述了如何使用它,包括在Java中使用_VALUE后缀的细微差别。似乎需要添加太多内容才能将其编辑到评论中,因此我添加了自己的答案。 - Edward Brey

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