Von Dart generierter Code
Alle Unterschiede zwischen proto2, proto3 und Editionen generiertem Code werden hervorgehoben – beachten Sie, dass diese Unterschiede im generierten Code liegen, wie in diesem Dokument beschrieben, nicht in der Basis-API, die in beiden Versionen gleich ist. Sie sollten die Sprachanleitung für proto2, die Sprachanleitung für proto3 oder die Sprachanleitung für Editionen lesen, bevor Sie dieses Dokument lesen.
Compiler-Aufruf
Der Protocol-Buffer-Compiler benötigt ein Plugin zum Generieren von Dart-Code. Wenn Sie es gemäß den Anweisungen installieren, erhalten Sie eine ausführbare Binärdatei protoc-gen-dart, die protoc bei Aufruf mit dem Befehlszeilenflag --dart_out verwendet. Das Flag --dart_out weist den Compiler an, wo die Dart-Quelldateien geschrieben werden sollen. Für eine .proto-Datei als Eingabe erzeugt der Compiler unter anderem eine .pb.dart-Datei.
Der Name der .pb.dart-Datei wird berechnet, indem der Name der .proto-Datei genommen und zwei Änderungen vorgenommen werden
- Die Erweiterung (
.proto) wird durch.pb.dartersetzt. Zum Beispiel führt eine Datei namensfoo.protozu einer Ausgabedatei namensfoo.pb.dart. - Der Proto-Pfad (angegeben mit dem Befehlszeilenflag
--proto_pathoder-I) wird durch den Ausgabepfad (angegeben mit dem Flag--dart_out) ersetzt.
Wenn Sie beispielsweise den Compiler wie folgt aufrufen
protoc --proto_path=src --dart_out=build/gen src/foo.proto src/bar/baz.proto
liest der Compiler die Dateien src/foo.proto und src/bar/baz.proto. Er erzeugt: build/gen/foo.pb.dart und build/gen/bar/baz.pb.dart. Der Compiler erstellt das Verzeichnis build/gen/bar automatisch, falls erforderlich, aber er erstellt nicht build oder build/gen; diese müssen bereits existieren.
Nachrichten
Angesichts einer einfachen Nachrichten Deklaration:
message Foo {}
Der Protocol-Buffer-Compiler generiert eine Klasse namens Foo, die die Klasse GeneratedMessage erweitert.
Die Klasse GeneratedMessage definiert Methoden, mit denen die gesamte Nachricht überprüft, manipuliert, gelesen oder geschrieben werden kann. Zusätzlich zu diesen Methoden definiert die Klasse Foo die folgenden Methoden und Konstruktoren
Foo(): Standardkonstruktor. Erstellt eine Instanz, bei der alle singulären Felder nicht gesetzt und wiederholte Felder leer sind.Foo.fromBuffer(...): Erstellt einFooaus serialisierten Protocol-Buffer-Daten, die die Nachricht darstellen.Foo.fromJson(...): Erstellt einFooaus einem JSON-String, der die Nachricht kodiert.Foo clone(): Erstellt eine tiefe Kopie der Felder in der Nachricht.Foo copyWith(void Function(Foo) updates): Erstellt eine beschreibbare Kopie dieser Nachricht, wendet dieupdatesdarauf an und markiert die Kopie schreibgeschützt, bevor sie zurückgegeben wird.static Foo create(): Factory-Funktion zum Erstellen eines einzelnenFoo.static PbList<Foo> createRepeated(): Factory-Funktion zum Erstellen einer Liste, die ein veränderliches wiederholtes Feld vonFoo-Elementen implementiert.static Foo getDefault(): Gibt eine Singleton-Instanz vonFoozurück, die identisch mit einer neu erstellten Instanz von Foo ist (d. h. alle singulären Felder sind nicht gesetzt und alle wiederholten Felder sind leer).
Verschachtelte Typen
Eine Nachricht kann innerhalb einer anderen Nachricht deklariert werden. Zum Beispiel:
message Foo {
message Bar {
}
}
In diesem Fall generiert der Compiler zwei Klassen: Foo und Foo_Bar.
Felder
Zusätzlich zu den in der vorherigen Sektion beschriebenen Methoden generiert der Protocol-Buffer-Compiler für jedes Feld, das in der .proto-Datei innerhalb der Nachricht definiert ist, Zugriffsmethoden.
Beachten Sie, dass die generierten Namen immer camelCase verwenden, auch wenn der Feldname in der .proto-Datei klein geschrieben mit Unterstrichen ist (wie es sein sollte). Die Groß-/Kleinschreibungsumwandlung funktioniert wie folgt
- Für jeden Unterstrich im Namen wird der Unterstrich entfernt und der folgende Buchstabe großgeschrieben.
- Wenn dem Namen ein Präfix angefügt wird (z. B. "has"), wird der erste Buchstabe großgeschrieben. Andernfalls wird er kleingeschrieben.
Daher wird für das Feld foo_bar_baz der Getter zu get fooBarBaz und eine Methode mit dem Präfix has wäre hasFooBarBaz.
Singuläre primitive Felder
Alle Felder haben explizite Präsenz in der Dart-Implementierung.
Für die folgende Felddefinition
int32 foo = 1;
generiert der Compiler die folgenden Zugriffsmethoden in der Nachrichtenklasse
int get foo: Gibt den aktuellen Wert des Feldes zurück. Wenn das Feld nicht gesetzt ist, wird der Standardwert zurückgegeben.bool hasFoo(): Gibttruezurück, wenn das Feld gesetzt ist.set foo(int value): Setzt den Wert des Feldes. Nach dem Aufruf dieser Methode gibthasFoo()truezurück undget foogibtvaluezurück.void clearFoo(): Löscht den Wert des Feldes. Nach dem Aufruf dieser Methode gibthasFoo()falsezurück undget foogibt den Standardwert zurück.Hinweis
Aufgrund einer Eigenart in der Dart-Proto3-Implementierung werden die folgenden Methoden auch dann generiert, wenn die implizite Präsenz konfiguriert ist.bool hasFoo(): Gibttruezurück, wenn das Feld gesetzt ist.Hinweis
Dieser Wert kann nicht wirklich vertraut werden, wenn das Proto in einer anderen Sprache serialisiert wurde, die implizite Präsenz unterstützt (z. B. Java). Obwohl Dart die Präsenz verfolgt, tun dies andere Sprachen nicht, und ein Roundtrip eines Feldes mit impliziter Präsenz und Nullwert führt dazu, dass es aus der Sicht von Dart "verschwindet".void clearFoo(): Löscht den Wert des Feldes. Nach dem Aufruf dieser Methode gibthasFoo()falsezurück undget foogibt den Standardwert zurück.
Für andere einfache Feldtypen wird der entsprechende Dart-Typ gemäß der Tabelle der skalaren Werttypen gewählt. Für Nachrichten- und Enums-Typen wird der Werttyp durch die Nachrichten- oder Enum-Klasse ersetzt.
Singuläre Nachrichtenfelder
Gegeben den Nachrichtentyp:
message Bar {}
Für eine Nachricht mit einem Bar-Feld
// proto2
message Baz {
optional Bar bar = 1;
// The generated code is the same result if required instead of optional.
}
// proto3 and editions
message Baz {
Bar bar = 1;
}
generiert der Compiler die folgenden Zugriffsmethoden in der Nachrichtenklasse
Bar get bar: Gibt den aktuellen Wert des Feldes zurück. Wenn das Feld nicht gesetzt ist, wird der Standardwert zurückgegeben.set bar(Bar value): Setzt den Wert des Feldes. Nach dem Aufruf dieser Methode gibthasBar()truezurück undget bargibtvaluezurück.bool hasBar(): Gibttruezurück, wenn das Feld gesetzt ist.void clearBar(): Löscht den Wert des Feldes. Nach dem Aufruf dieser Methode gibthasBar()falsezurück undget bargibt den Standardwert zurück.Bar ensureBar(): Setztbarauf die leere Instanz, wennhasBar()falsezurückgibt, und gibt dann den Wert vonbarzurück. Nach dem Aufruf dieser Methode gibthasBar()truezurück.
Wiederholte Felder
Für diese Felddefinition:
repeated int32 foo = 1;
Der Compiler wird generieren
List<int> get foo: Gibt die Liste zurück, die das Feld unterstützt. Wenn das Feld nicht gesetzt ist, wird eine leere Liste zurückgegeben. Änderungen an der Liste werden im Feld reflektiert.
Int64-Felder
Für diese Felddefinition:
int64 bar = 1;
Der Compiler wird generieren
Int64 get bar: Gibt einInt64-Objekt zurück, das den Feldwert enthält.
Beachten Sie, dass Int64 nicht in den Kernbibliotheken von Dart enthalten ist. Um mit diesen Objekten zu arbeiten, müssen Sie möglicherweise die Dart fixnum-Bibliothek importieren
import 'package:fixnum/fixnum.dart';
Map-Felder
Gegeben eine map-Felddefinition wie diese
map<int32, int32> map_field = 1;
Der Compiler generiert den folgenden Getter
Map<int, int> get mapField: Gibt die Dart-Map zurück, die das Feld unterstützt. Wenn das Feld nicht gesetzt ist, wird eine leere Map zurückgegeben. Änderungen an der Map werden im Feld reflektiert.
Any
Gegeben ein Any-Feld wie dieses
import "google/protobuf/any.proto";
message ErrorStatus {
string message = 1;
google.protobuf.Any details = 2;
}
In unserem generierten Code gibt der Getter für das Feld details eine Instanz von com.google.protobuf.Any zurück. Dies bietet die folgenden speziellen Methoden zum Packen und Entpacken der Werte von Any
/// Unpacks the message in [value] into [instance].
///
/// Throws a [InvalidProtocolBufferException] if [typeUrl] does not correspond
/// to the type of [instance].
///
/// A typical usage would be `any.unpackInto(new Message())`.
///
/// Returns [instance].
T unpackInto<T extends GeneratedMessage>(T instance,
{ExtensionRegistry extensionRegistry = ExtensionRegistry.EMPTY});
/// Returns `true` if the encoded message matches the type of [instance].
///
/// Can be used with a default instance:
/// `any.canUnpackInto(Message.getDefault())`
bool canUnpackInto(GeneratedMessage instance);
/// Creates a new [Any] encoding [message].
///
/// The [typeUrl] will be [typeUrlPrefix]/`fullName` where `fullName` is
/// the fully qualified name of the type of [message].
static Any pack(GeneratedMessage message,
{String typeUrlPrefix = 'type.googleapis.com'});
Oneof
Gegeben eine oneof-Definition wie diese
message Foo {
oneof test {
string name = 1;
SubMessage sub_message = 2;
}
}
Der Compiler generiert den folgenden Dart-Enum-Typ
enum Foo_Test { name, subMessage, notSet }
Zusätzlich generiert er diese Methoden
Foo_Test whichTest(): Gibt den Enum zurück, der angibt, welches Feld gesetzt ist. GibtFoo_Test.notSetzurück, wenn keines davon gesetzt ist.void clearTest(): Löscht den Wert des aktuell gesetzten Oneof-Feldes (falls vorhanden) und setzt den Oneof-Fall aufFoo_Test.notSet.
Für jedes Feld innerhalb der Oneof-Definition werden die regulären Feldzugriffsmethoden generiert. Zum Beispiel für name
String get name: Gibt den aktuellen Wert des Feldes zurück, wenn der Oneof-FallFoo_Test.nameist. Andernfalls wird der Standardwert zurückgegeben.set name(String value): Setzt den Wert des Feldes und setzt den Oneof-Fall aufFoo_Test.name. Nach dem Aufruf dieser Methode gibtget namevaluezurück undwhichTest()gibtFoo_Test.namezurück.void clearName(): Es wird nichts geändert, wenn der Oneof-Fall nichtFoo_Test.nameist. Andernfalls wird der Wert des Feldes gelöscht. Nach dem Aufruf dieser Methode gibtget nameden Standardwert zurück undwhichTest()gibtFoo_Test.notSetzurück.
Aufzählungen (Enums)
Gegeben eine Enum-Definition wie
enum Color {
COLOR_UNSPECIFIED = 0;
COLOR_RED = 1;
COLOR_GREEN = 2;
COLOR_BLUE = 3;
}
Der Protocol-Buffer-Compiler generiert eine Klasse namens Color, die die Klasse ProtobufEnum erweitert. Die Klasse enthält eine static const Color für jeden der vier Werte sowie eine static const List<Color>, die die Werte enthält.
static const List<Color> values = <Color> [
COLOR_UNSPECIFIED,
COLOR_RED,
COLOR_GREEN,
COLOR_BLUE,
];
Sie enthält auch die folgende Methode
static Color? valueOf(int value): Gibt dieColorzurück, die dem angegebenen numerischen Wert entspricht.
Jeder Wert hat die folgenden Eigenschaften
name: Der Name des Enums, wie er in der .proto-Datei angegeben ist.value: Der ganzzahlige Wert des Enums, wie er in der .proto-Datei angegeben ist.
Beachten Sie, dass die .proto-Sprache es erlaubt, dass mehrere Enum-Symbole den gleichen numerischen Wert haben. Symbole mit demselben numerischen Wert sind Synonyme. Zum Beispiel
enum Foo {
BAR = 0;
BAZ = 0;
}
In diesem Fall ist BAZ ein Synonym für BAR und wird wie folgt definiert
static const Foo BAZ = BAR;
Ein Enum kann verschachtelt innerhalb eines Nachrichtentyps definiert werden. Zum Beispiel, gegeben eine Enum-Definition wie
message Bar {
enum Color {
COLOR_UNSPECIFIED = 0;
COLOR_RED = 1;
COLOR_GREEN = 2;
COLOR_BLUE = 3;
}
}
Der Protocol-Buffer-Compiler generiert eine Klasse namens Bar, die GeneratedMessage erweitert, und eine Klasse namens Bar_Color, die ProtobufEnum erweitert.
Erweiterungen (nicht in proto3 verfügbar)
Gegeben eine Datei foo_test.proto, die eine Nachricht mit einem Erweiterungsbereich und eine Top-Level-Erweiterungsdefinition enthält
message Foo {
extensions 100 to 199;
}
extend Foo {
optional int32 bar = 101;
}
Der Protocol-Buffer-Compiler generiert zusätzlich zur Klasse Foo eine Klasse Foo_test, die eine static Extension für jedes Erweiterungsfeld in der Datei sowie eine Methode zur Registrierung aller Erweiterungen in einem ExtensionRegistry enthält
static final Extension barstatic void registerAllExtensions(ExtensionRegistry registry): Registriert alle definierten Erweiterungen im angegebenen Registry.
Die Erweiterungszugreifer von Foo können wie folgt verwendet werden
Foo foo = Foo();
foo.setExtension(Foo_test.bar, 1);
assert(foo.hasExtension(Foo_test.bar));
assert(foo.getExtension(Foo_test.bar)) == 1);
Erweiterungen können auch verschachtelt innerhalb einer anderen Nachricht deklariert werden
message Baz {
extend Foo {
int32 bar = 124;
}
}
In diesem Fall wird die Erweiterung bar stattdessen als statisches Mitglied der Klasse Baz deklariert.
Beim Parsen einer Nachricht, die Erweiterungen enthalten kann, müssen Sie eine ExtensionRegistry bereitstellen, in der Sie alle Erweiterungen registriert haben, die Sie parsen möchten. Andernfalls werden diese Erweiterungen wie unbekannte Felder behandelt. Zum Beispiel
ExtensionRegistry registry = ExtensionRegistry();
registry.add(Baz.bar);
Foo foo = Foo.fromBuffer(input, registry);
Wenn Sie bereits eine geparste Nachricht mit unbekannten Feldern haben, können Sie reparseMessage für eine ExtensionRegistry verwenden, um die Nachricht neu zu parsen. Wenn die Menge der unbekannten Felder Erweiterungen enthält, die im Registry vorhanden sind, werden diese Erweiterungen geparst und aus der Menge der unbekannten Felder entfernt. Bereits in der Nachricht vorhandene Erweiterungen bleiben erhalten.
Foo foo = Foo.fromBuffer(input);
ExtensionRegistry registry = ExtensionRegistry();
registry.add(Baz.bar);
Foo reparsed = registry.reparseMessage(foo);
Seien Sie sich bewusst, dass diese Methode zum Abrufen von Erweiterungen insgesamt aufwändiger ist. Wo immer möglich, empfehlen wir die Verwendung von ExtensionRegistry mit allen benötigten Erweiterungen bei GeneratedMessage.fromBuffer.
Services
Gegeben eine Service-Definition
service Foo {
rpc Bar(FooRequest) returns(FooResponse);
}
Der Protocol-Buffer-Compiler kann mit der Option `grpc` (z. B. `--dart_out=grpc:output_folder`) aufgerufen werden. In diesem Fall generiert er Code zur Unterstützung von gRPC. Weitere Details finden Sie im gRPC Dart Quickstart-Leitfaden.