Von Dart generierter Code

Beschreibt, welcher Dart-Code vom Protocol-Buffer-Compiler für eine bestimmte Protokolldefinition generiert wird.

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.dart ersetzt. Zum Beispiel führt eine Datei namens foo.proto zu einer Ausgabedatei namens foo.pb.dart.
  • Der Proto-Pfad (angegeben mit dem Befehlszeilenflag --proto_path oder -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 ein Foo aus serialisierten Protocol-Buffer-Daten, die die Nachricht darstellen.
  • Foo.fromJson(...): Erstellt ein Foo aus 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 die updates darauf an und markiert die Kopie schreibgeschützt, bevor sie zurückgegeben wird.
  • static Foo create(): Factory-Funktion zum Erstellen eines einzelnen Foo.
  • static PbList<Foo> createRepeated(): Factory-Funktion zum Erstellen einer Liste, die ein veränderliches wiederholtes Feld von Foo-Elementen implementiert.
  • static Foo getDefault(): Gibt eine Singleton-Instanz von Foo zurü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

  1. Für jeden Unterstrich im Namen wird der Unterstrich entfernt und der folgende Buchstabe großgeschrieben.
  2. 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(): Gibt true zurück, wenn das Feld gesetzt ist.

  • set foo(int value): Setzt den Wert des Feldes. Nach dem Aufruf dieser Methode gibt hasFoo() true zurück und get foo gibt value zurück.

  • void clearFoo(): Löscht den Wert des Feldes. Nach dem Aufruf dieser Methode gibt hasFoo() false zurück und get foo gibt den Standardwert zurück.

  • bool hasFoo(): Gibt true zurück, wenn das Feld gesetzt ist.

  • void clearFoo(): Löscht den Wert des Feldes. Nach dem Aufruf dieser Methode gibt hasFoo() false zurück und get foo gibt 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 gibt hasBar() true zurück und get bar gibt value zurück.
  • bool hasBar(): Gibt true zurück, wenn das Feld gesetzt ist.
  • void clearBar(): Löscht den Wert des Feldes. Nach dem Aufruf dieser Methode gibt hasBar() false zurück und get bar gibt den Standardwert zurück.
  • Bar ensureBar(): Setzt bar auf die leere Instanz, wenn hasBar() false zurückgibt, und gibt dann den Wert von bar zurück. Nach dem Aufruf dieser Methode gibt hasBar() true zurü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 ein Int64-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. Gibt Foo_Test.notSet zurück, wenn keines davon gesetzt ist.
  • void clearTest(): Löscht den Wert des aktuell gesetzten Oneof-Feldes (falls vorhanden) und setzt den Oneof-Fall auf Foo_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-Fall Foo_Test.name ist. Andernfalls wird der Standardwert zurückgegeben.
  • set name(String value): Setzt den Wert des Feldes und setzt den Oneof-Fall auf Foo_Test.name. Nach dem Aufruf dieser Methode gibt get name value zurück und whichTest() gibt Foo_Test.name zurück.
  • void clearName(): Es wird nichts geändert, wenn der Oneof-Fall nicht Foo_Test.name ist. Andernfalls wird der Wert des Feldes gelöscht. Nach dem Aufruf dieser Methode gibt get name den Standardwert zurück und whichTest() gibt Foo_Test.notSet zurü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 die Color zurü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 bar
  • static 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.