列舉行為
列舉在不同的語言程式庫中的行為不同。本主題涵蓋了不同的行為,以及將 protobuf 移至跨所有語言一致狀態的計畫。如果您正在尋找有關如何一般使用列舉的資訊,請參閱 proto2 和 proto3 語言指南主題中的相關章節。
定義
列舉有兩種不同的類型(開放式和封閉式)。它們的行為相同,但在處理未知值時除外。實際上,這意味著簡單的情況下運作方式相同,但某些邊緣情況具有有趣的含義。
為了方便解釋,假設我們有以下 .proto
檔案(我們故意不指定這是 syntax = "proto2"
還是 syntax = "proto3"
檔案)
enum Enum {
A = 0;
B = 1;
}
message Msg {
optional Enum enum = 1;
}
開放式和封閉式之間的區別可以用一個問題來概括
當程式剖析包含值為
2
的欄位 1 的二進位資料時會發生什麼事?
- 開放式列舉會剖析值
2
並將其直接儲存在欄位中。存取器會將欄位報告為已設定,並會傳回代表2
的值。 - 封閉式列舉會剖析值
2
並將其儲存在訊息的未知欄位集中。存取器會將欄位報告為未設定,並會傳回列舉的預設值。
封閉式列舉的影響
當剖析重複欄位時,封閉式列舉的行為會產生意想不到的後果。當剖析 repeated Enum
欄位時,所有未知值都會放置在 未知欄位 集中。當它被序列化時,這些未知值會再次寫入,但不會在清單中的原始位置。例如,假設有 .proto
檔案
enum Enum {
A = 0;
B = 1;
}
message Msg {
repeated Enum r = 1;
}
包含欄位 1 的值 [0, 2, 1, 2]
的線路格式會進行剖析,使重複欄位包含 [0, 1]
,並且值 [2, 2]
會以未知欄位的形式儲存。重新序列化訊息後,線路格式會對應到 [0, 1, 2, 2]
。
同樣地,當值為未知時,值為封閉式列舉的地圖會將整個條目(鍵和值)放置在未知欄位中。
歷史記錄
在引入 syntax = "proto3"
之前,所有列舉都是封閉式。Proto3 引入了開放式列舉,特別是因為封閉式列舉會導致意想不到的行為。
規格
以下指定了 protobuf 相容實作的行為。因為這很微妙,所以許多實作都不符合規範。有關不同實作的行為方式的詳細資訊,請參閱 已知問題。
- 當
proto2
檔案匯入proto2
檔案中定義的列舉時,該列舉應視為封閉式。 - 當
proto3
檔案匯入proto3
檔案中定義的列舉時,該列舉應視為開放式。 - 當
proto3
檔案匯入proto2
檔案中定義的列舉時,protoc
編譯器會產生錯誤。 - 當
proto2
檔案匯入proto3
檔案中定義的列舉時,該列舉應視為開放式。
已知問題
C++
所有已知的 C++ 版本都不符合規範。當 proto2
檔案匯入 proto3
檔案中定義的列舉時,C++ 會將該欄位視為封閉式列舉。在版本下,此行為由已棄用的欄位功能 features.(pb.cpp).legacy_closed_enum
表示。有兩種選項可移至符合規範的行為
- 移除欄位功能。這是建議的方法,但可能會導致執行階段行為變更。如果沒有該功能,無法辨識的整數最終會儲存在轉換為列舉類型的欄位中,而不是放入未知欄位集中。
- 將列舉變更為封閉式。不建議這樣做,如果其他任何人正在使用列舉,則可能會導致執行階段行為。無法辨識的整數最終會放入未知欄位集中,而不是那些欄位中。
C#
所有已知的 C# 版本都不符合規範。C# 將所有列舉視為開放式。
Java
所有已知的 Java 版本都不符合規範。當 proto2
檔案匯入 proto3
檔案中定義的列舉時,Java 會將該欄位視為封閉式列舉。
在版本下,此行為由已棄用的欄位功能 features.(pb.java).legacy_closed_enum
表示。有兩種選項可移至符合規範的行為
- 移除欄位功能。這可能會導致執行階段行為變更。如果沒有該功能,無法辨識的整數最終會儲存在欄位中,並且列舉 getter 會傳回
UNRECOGNIZED
值。之前,這些值會放入未知欄位集中。 - 將列舉變更為封閉式。如果其他任何人正在使用它,他們可能會看到執行階段行為變更。無法辨識的整數最終會放入未知欄位集中,而不是那些欄位中。
注意: Java 對於開放式列舉的處理具有令人驚訝的邊緣情況。假設有以下定義
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)
。當傳遞Enum.UNRECOGNIZED
時,方法setName
會擲回例外狀況,而setNameValue
會接受2
。
Kotlin
所有已知的 Kotlin 版本都不符合規範。當 proto2
檔案匯入 proto3
檔案中定義的列舉時,Kotlin 會將該欄位視為封閉式列舉。
Kotlin 建構於 Java 之上,並共用其所有奇怪之處。
Go
所有已知的 Go 版本都不符合規範。Go 將所有列舉視為開放式。
JSPB
所有已知的 JSPB 版本都不符合規範。JSPB 將所有列舉視為開放式。
PHP
PHP 符合規範。
Python
在 4.22.0 之後,Python 符合規範。
在 4.21.x 中,Python 預設符合規範,但設定 PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION=python
會導致其不符合規範。
在 4.21.0 之前,Python 不符合規範。
當 proto2
檔案匯入 proto3
檔案中定義的列舉時,不符合規範的 Python 版本會將該欄位視為封閉式列舉。
Ruby
所有已知的 Ruby 版本都不符合規範。Ruby 將所有列舉視為開放式。
Objective-C
在 22.0 之後,Objective-C 符合規範。
在 22.0 之前,Objective-C 不符合規範。當 proto2
檔案匯入 proto3
檔案中定義的列舉時,它會將該欄位視為封閉式列舉。
Swift
Swift 符合規範。
Dart
Dart 將所有列舉視為封閉式。