Enum Verhalten
Enums verhalten sich in verschiedenen Sprachbibliotheken unterschiedlich. Dieses Thema behandelt die verschiedenen Verhaltensweisen sowie die Pläne, Protobufs in einen Zustand zu bringen, der über alle Sprachen hinweg konsistent ist. Wenn Sie nach Informationen zur allgemeinen Verwendung von Enums suchen, siehe die entsprechenden Abschnitte in den Sprachleitfäden für proto2, proto3 und editions 2023.
Definitionen
Enums haben zwei verschiedene Varianten (offen und geschlossen). Sie verhalten sich identisch, außer bei der Behandlung unbekannter Werte. Praktisch bedeutet dies, dass einfache Fälle gleich funktionieren, aber einige Eckfälle interessante Auswirkungen haben.
Zur Erklärung nehmen wir an, wir haben die folgende .proto-Datei (wir geben bewusst nicht an, ob es sich um eine Datei mit syntax = "proto2", syntax = "proto3" oder edition = "2023" handelt)
enum Enum {
A = 0;
B = 1;
}
message Msg {
optional Enum enum = 1;
}
Die Unterscheidung zwischen offen und geschlossen kann in einer einzigen Frage zusammengefasst werden
Was passiert, wenn ein Programm Binärdaten parst, die Feld 1 mit dem Wert
2enthalten?
- Offene Enums parsen den Wert
2und speichern ihn direkt im Feld. Accessoren melden, dass das Feld gesetzt ist, und geben etwas zurück, das2repräsentiert. - Geschlossene Enums parsen den Wert
2und speichern ihn im unbekannten Feld-Set der Nachricht. Accessoren melden, dass das Feld nicht gesetzt ist, und geben den Standardwert des Enums zurück.
Auswirkungen von geschlossenen Enums
Das Verhalten von geschlossenen Enums hat unerwartete Folgen beim Parsen eines wiederholten Feldes. Wenn ein repeated Enum-Feld geparst wird, werden alle unbekannten Werte in das unbekannte Feld-Set platziert. Wenn es serialisiert wird, werden diese unbekannten Werte erneut geschrieben, aber nicht an ihrer ursprünglichen Position in der Liste. Zum Beispiel, gegeben die .proto-Datei
enum Enum {
A = 0;
B = 1;
}
message Msg {
repeated Enum r = 1;
}
Ein Wire-Format, das die Werte [0, 2, 1, 2] für Feld 1 enthält, wird so geparst, dass das wiederholte Feld [0, 1] enthält und der Wert [2, 2] als unbekanntes Feld gespeichert wird. Nach der erneuten Serialisierung der Nachricht entspricht das Wire-Format [0, 1, 2, 2].
Ähnlich werden Maps mit geschlossenen Enums als Wert ganze Einträge (Schlüssel und Wert) in die unbekannten Felder platzieren, wenn der Wert unbekannt ist.
Historie
Vor der Einführung von syntax = "proto3" waren alle Enums geschlossen. Proto3 und Editions verwenden offene Enums gerade wegen des unerwarteten Verhaltens, das geschlossene Enums verursachen. Sie können features.enum_type verwenden, um Editions-Enums explizit als offen zu setzen, falls erforderlich.
Spezifikation
Das Folgende spezifiziert das Verhalten konformer Implementierungen für Protobuf. Da dies subtil ist, sind viele Implementierungen nicht konform. Siehe Bekannte Probleme für Details, wie sich verschiedene Implementierungen verhalten.
- Wenn eine
proto2-Datei ein Enum importiert, das in einerproto2-Datei definiert ist, sollte dieses Enum als geschlossen behandelt werden. - Wenn eine
proto3-Datei ein Enum importiert, das in einerproto3-Datei definiert ist, sollte dieses Enum als offen behandelt werden. - Wenn eine
proto3-Datei ein Enum importiert, das in einerproto2-Datei definiert ist, gibt derprotoc-Compiler einen Fehler aus. - Wenn eine
proto2-Datei ein Enum importiert, das in einerproto3-Datei definiert ist, sollte dieses Enum als offen behandelt werden.
Editions respektieren das Verhalten, das das Enum in der importierten Datei hatte. Proto2-Enums werden immer als geschlossen behandelt, Proto3-Enums immer als offen, und beim Importieren aus einer anderen Editions-Datei wird die Feature-Einstellung verwendet.
Bekannte Probleme
C++
Alle bekannten C++-Releases sind nicht konform. Wenn eine proto2-Datei ein Enum importiert, das in einer proto3-Datei definiert ist, behandelt C++ dieses Feld als geschlossenes Enum. Unter Editions wird dieses Verhalten durch das veraltete Feld-Feature features.(pb.cpp).legacy_closed_enum dargestellt. Es gibt zwei Optionen für ein konformes Verhalten
- Entfernen Sie das Feld-Feature. Dies ist der empfohlene Ansatz, kann aber zu Laufzeitänderungen führen. Ohne das Feature werden nicht erkannte ganze Zahlen in das Feld gespeichert, das in den Enum-Typ gecastet wird, anstatt in das unbekannte Feld-Set zu gelangen.
- Ändern Sie das Enum auf geschlossen. Dies wird abgeraten und kann zu Laufzeitänderungen führen, wenn irgendjemand anderes das Enum verwendet. Nicht erkannte ganze Zahlen gelangen anstelle dieser Felder in das unbekannte Feld-Set.
C#
Alle bekannten C#-Releases sind nicht konform. C# behandelt alle Enums als offen.
Java
Alle bekannten Java-Releases sind nicht konform. Wenn eine proto2-Datei ein Enum importiert, das in einer proto3-Datei definiert ist, behandelt Java dieses Feld als geschlossenes Enum.
Unter Editions wird dieses Verhalten durch das veraltete Feld-Feature features.(pb.java).legacy_closed_enum dargestellt). Es gibt zwei Optionen für ein konformes Verhalten
- Entfernen Sie das Feld-Feature. Dies kann zu Laufzeitänderungen führen. Ohne das Feature werden nicht erkannte ganze Zahlen im Feld gespeichert und der Wert
UNRECOGNIZEDwird vom Enum-Getter zurückgegeben. Zuvor gelangten diese Werte in das unbekannte Feld-Set. - Ändern Sie das Enum auf geschlossen. Wenn irgendjemand anderes es verwendet, kann es zu Laufzeitänderungen kommen. Nicht erkannte ganze Zahlen gelangen anstelle dieser Felder in das unbekannte Feld-Set.
HINWEIS: Javabehandlung von offenen Enums hat überraschende Randfälle. Angesichts der folgenden Definitionen
syntax = "proto3"; enum Enum { A = 0; B = 1; } message Msg { repeated Enum name = 1; }Java generiert die Methoden
Enum getName()undint getNameValue(). Die MethodegetNamegibtEnum.UNRECOGNIZEDfür Werte außerhalb des bekannten Satzes zurück (wie z. B.2), währendgetNameValue2zurückgibt.Ähnlich generiert Java die Methoden
Builder setName(Enum value)undBuilder setNameValue(int value). Die MethodesetNamelöst eine Ausnahme aus, wennEnum.UNRECOGNIZEDübergeben wird, währendsetNameValue2akzeptiert.
Kotlin
Alle bekannten Kotlin-Releases sind nicht konform. Wenn eine proto2-Datei ein Enum importiert, das in einer proto3-Datei definiert ist, behandelt Kotlin dieses Feld als geschlossenes Enum.
Kotlin basiert auf Java und teilt all dessen Eigenheiten.
Go
Alle bekannten Go-Releases sind nicht konform. Go behandelt alle Enums als offen.
JSPB
Alle bekannten JSPB-Releases sind nicht konform. JSPB behandelt alle Enums als offen.
PHP
PHP ist konform.
Python
Python ist ab Version 4.22.0 (veröffentlicht 2023 Q1) konform.
Ältere, nicht mehr unterstützte Versionen sind nicht konform. Wenn eine proto2-Datei ein Enum importiert, das in einer proto3-Datei definiert ist, behandeln nicht-konforme Python-Versionen dieses Feld als geschlossenes Enum.
Ruby
Alle bekannten Ruby-Releases sind nicht konform. Ruby behandelt alle Enums als offen.
Objective-C
Objective-C ist ab Version 3.22.0 (veröffentlicht 2023 Q1) konform.
Ältere, nicht mehr unterstützte Versionen sind nicht konform. Wenn eine proto2-Datei ein Enum importiert, das in einer proto3-Datei definiert ist, behandeln nicht-konforme ObjC-Versionen dieses Feld als geschlossenes Enum.
Swift
Swift ist konform.
Dart
Dart behandelt alle Enums als geschlossen.