分类:proto| 发布时间:2025-01-16 08:37:00
解释 Protocol Buffers 中枚举目前的工作方式以及它们应该如何工作。
枚举在不同的语言库中表现出不同的行为。 本主题涵盖了这些不同的行为以及将 Protocol Buffers 迁移到跨所有语言一致的状态的计划。 如果您正在寻找有关如何一般使用枚举的信息,请参阅 proto2 和 proto3 语言指南主题中的相应部分。
枚举有两种不同的风格 开放(Open)和封闭(Closed)。 除了处理未知值的方式之外,它们的行为完全相同。 实际上,这意味着简单的用例表现相同,但一些极端情况会有有趣的影响。
为了解释,让我们假设我们有以下 .proto
文件(我们目前故意不指定这是 syntax = "proto2"
还是 syntax = "proto3"
文件):
enum Enum {
A = 0;
B = 1;
}
message Msg {
optional Enum enum = 1;
}
开放和封闭之间的区别可以用一个问题来概括:
当程序解析包含字段1和值2的二进制数据时会发生什么?
当解析重复字段时,封闭枚举的行为会产生意想不到的后果。
当解析 重复的 Enum
字段时,所有未知值都将被放置在未知字段集中。
当序列化时,这些未知值将再次被写入,但不会在列表中的原始位置。
例如,给定以下 .proto
文件:
enum Enum {
A = 0;
B = 1;
}
message Msg {
repeated Enum r = 1;
}
字段 1 的值为 [0, 2, 1, 2] 的传输格式将被解析,使得 repeated 字段包含 [0, 1], 而值 [2, 2] 将最终存储为未知字段。 重新序列化消息后,传输格式将变为 [0, 1, 2, 2]。
类似地,对于其值为封闭枚举的映射,每当值为未知时,整个条目(键和值)将被放置在未知字段中。
在引入 syntax = "proto3"
之前,所有枚举都是 封闭(closed)
的。
Proto3 引入 开放(open)
枚举正是因为 封闭
枚举会导致意外的行为。
以下内容指定了符合 protobuf 规范的实现的行为。 由于这一点比较微妙,许多实现都不符合规范。 有关不同实现的行为,请参阅 已知问题 。
proto2
文件导入在另一个 proto2
文件中定义的枚举时,该枚举应被视为 封闭(closed)
。proto3
文件导入在另一个 proto3
文件中定义的枚举时,该枚举应被视为 开放(open)
。proto3
文件导入在 proto2
文件中定义的枚举时, protoc
编译器将产生错误。proto2
文件导入在 proto3
文件中定义的枚举时,该枚举应被视为 开放(open)
。所有已知的 C++ 版本都不符合规范。
当一个 proto2
文件导入在 proto3
文件中定义的枚举时,C++ 将该字段视为 封闭(closed)
枚举。
在 editions 中,此行为由弃用的字段特性 features.(pb.cpp).legacy_closed_enum
表示。
有两种方法可以迁移到符合规范的行为:
所有已知的 C# 版本都不符合规范。 C# 将所有枚举视为 开放(open)。
所有已知的 Java 版本都不符合规范。
当一个 proto2
文件导入在 proto3
文件中定义的枚举时,Java 将该字段视为 封闭(closed)
枚举。
在 editions,这种行为由一个名为 features.(pb.java).legacy_closed_enum
的弃用字段特性控制。
为了实现符合规范的行为,有两个可选方案:
UNRECOGNIZED
值。
之前,这些值将被放入未知字段集中。注意: Java 处理
开放(open)
枚举的方式存在令人惊讶的边界情况。 给定以下定义:
syntax = "proto3";
enum Enum {
A = 0;
B = 1;
}
message Msg {
repeated Enum name = 1;
}
Java 将生成方法 Enum getName()
和 int getNameValue()
。
方法 getName
将为不在已知集合中的值(例如 2)返回 Enum.UNRECOGNIZED
,而 getNameValue 将返回 2。
类似地,Java 将生成方法 Builder setName(Enum value)
和 Builder setNameValue(int value)
。
方法 setName
在传递 Enum.UNRECOGNIZED
时将抛出异常,而 setNameValue
将接受 2。
所有已知的 Kotlin 版本都不符合规范。
当一个 proto2
文件导入在 proto3
文件中定义的枚举时,Kotlin 将该字段视为 封闭(closed)
枚举。
Kotlin 构建于 Java 之上,并共享其所有奇异之处。
所有已知的 Go 版本都不符合规范。
Go 将所有枚举视为 开放(open)
。
所有已知的 JSPB 版本都不符合规范。
JSPB 将所有枚举视为 开放(open)
。
PHP 是符合规范的。
在 4.22.0 发布版~2023-02-16,之后 Python 是符合规范的。
在 4.21.x 版本中,Python 默认情况下符合规范,但设置 PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION=python
将导致其不符合规范。
在 4.21.0 之前,Python 是不符合规范的。
当一个 proto2
文件导入在 proto3
文件中定义的枚举时,不符合规范的 Python 版本将该字段视为 封闭(closed)
枚举。
所有已知的 Ruby 版本都不符合规范。
Ruby 将所有枚举视为 开放(open)
。
在 22.0 之后, Objective-C 是符合规范的。
在 22.0 之前,Objective-C 是不符合规范的。
当一个 proto2
文件导入了一个在 proto3
文件中定义的枚举时,它会将该字段视为一个 闭合
的枚举。
Swift 是符合规范的。
Dart 将所有 enums 视为 闭合
枚举。