|
@@ -0,0 +1,184 @@
|
|
|
+Proto2 support in Google.Protobuf is finally here! This document outlines the new changes brought about
|
|
|
+by this initial release of proto2 support. The generated code and public API associated with proto2
|
|
|
+is experimental and subject to change in the future. APIs may be added, removed, or adjusted as feedback is received.
|
|
|
+Generated code may also be modified, adding, removing, or adjusting APIs as feedback is received.
|
|
|
+
|
|
|
+# Generated code
|
|
|
+
|
|
|
+### Messages
|
|
|
+
|
|
|
+Messages in proto2 files are very similar to their proto3 counterparts. However, they have some added properties
|
|
|
+and methods to handle field presence.
|
|
|
+
|
|
|
+A normal single value proto2 fields will have a normal property for getting and setting, as well as a
|
|
|
+`HasValue` property for checking presence, and a `Clear` method for clearing the value.
|
|
|
+
|
|
|
+```proto
|
|
|
+message Foo {
|
|
|
+ optional Bar bar = 1;
|
|
|
+ required Baz baz = 2;
|
|
|
+}
|
|
|
+```
|
|
|
+```cs
|
|
|
+var foo = new Foo();
|
|
|
+Assert.IsNull(foo.Bar);
|
|
|
+Assert.False(foo.HasBar);
|
|
|
+foo.Bar = new Bar();
|
|
|
+Assert.True(foo.HasBar);
|
|
|
+foo.ClearBar();
|
|
|
+```
|
|
|
+
|
|
|
+### Messages with extension ranges
|
|
|
+
|
|
|
+Messages which define extension ranges implement the `IExtendableMessage` interface as shown below.
|
|
|
+See inline comments for more info.
|
|
|
+
|
|
|
+```cs
|
|
|
+public interface IExtendableMessage<T> : IMessage<T> where T : IExtendableMessage<T>
|
|
|
+{
|
|
|
+ // Gets the value of a single value extension. If the extension isn't present, this returns the default value.
|
|
|
+ TValue GetExtension<TValue>(Extension<T, TValue> extension);
|
|
|
+ // Gets the value of a repeated extension. If the extension hasn't been set, this returns null to prevent unnecessary allocations.
|
|
|
+ RepeatedField<TValue> GetExtension<TValue>(RepeatedExtension<T, TValue> extension);
|
|
|
+ // Gets the value of a repeated extension. This will initialize the value of the repeated field and will never return null.
|
|
|
+ RepeatedField<TValue> GetOrInitializeExtension<TValue>(RepeatedExtension<T, TValue> extension);
|
|
|
+ // Sets the value of the extension
|
|
|
+ void SetExtension<TValue>(Extension<T, TValue> extension, TValue value);
|
|
|
+ // Returns whether the extension is present in the message
|
|
|
+ bool HasExtension<TValue>(Extension<T, TValue> extension);
|
|
|
+ // Clears the value of the extension, removing it from the message
|
|
|
+ void ClearExtension<TValue>(Extension<T, TValue> extension);
|
|
|
+ // Clears the value of the repeated extension, removing it from the message. Calling GetExtension after this will always return null.
|
|
|
+ void ClearExtension<TValue>(RepeatedExtension<T, TValue> extension);
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+### Extensions
|
|
|
+
|
|
|
+Extensions are generated in static containers like reflection classes and type classes.
|
|
|
+For example for a file called `foo.proto` containing extensions in the file scope, a
|
|
|
+`FooExtensions` class is created containing the extensions defined in the file scope.
|
|
|
+For easy access, this class can be used with `using static` to bring all extensions into scope.
|
|
|
+
|
|
|
+```proto
|
|
|
+option csharp_namespace = "FooBar";
|
|
|
+extend Foo {
|
|
|
+ optional Baz foo_ext = 124;
|
|
|
+}
|
|
|
+message Baz {
|
|
|
+ extend Foo {
|
|
|
+ repeated Baz repeated_foo_ext = 125;
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+```cs
|
|
|
+public static partial class FooExtensions {
|
|
|
+ public static readonly Extension<Foo, Baz> FooExt = /* initialization */;
|
|
|
+}
|
|
|
+
|
|
|
+public partial class Baz {
|
|
|
+ public partial static class Extensions {
|
|
|
+ public static readonly RepeatedExtension<Foo, Baz> RepeatedFooExt = /* initialization */;
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+```cs
|
|
|
+using static FooBar.FooExtensions;
|
|
|
+using static FooBar.Baz.Extensions;
|
|
|
+
|
|
|
+var foo = new Foo();
|
|
|
+foo.SetExtension(FooExt, new Baz());
|
|
|
+foo.GetOrInitializeExtension(RepeatedFooExt).Add(new Baz());
|
|
|
+```
|
|
|
+
|
|
|
+# APIs
|
|
|
+
|
|
|
+### Message initialization
|
|
|
+
|
|
|
+Checking message initialization is not handled automatically by the library, but can be done manually via the
|
|
|
+`IsInitialized` extension method in `MessageExtensions`. Please note, parsers and input streams don't check messages
|
|
|
+for initialization on their own and throw errors. Instead it's up to you to handle messages with missing required fields
|
|
|
+in whatever way you see fit.
|
|
|
+
|
|
|
+### Extension registries
|
|
|
+
|
|
|
+Just like in Java, extension registries can be constructed to parse extensions when reading new messages
|
|
|
+from input streams. The API is fairly similar to the Java API with some added bonuses with C# syntax sugars.
|
|
|
+
|
|
|
+```proto
|
|
|
+message Baz {
|
|
|
+ extend Foo {
|
|
|
+ optional Baz foo_ext = 124;
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+```cs
|
|
|
+var registry = new ExtensionRegistry()
|
|
|
+{
|
|
|
+ Baz.Extensions.FooExt
|
|
|
+};
|
|
|
+var foo = Foo.Factory.WithExtensionRegistry(registry).ParseFrom(input);
|
|
|
+Assert.True(foo.HasExtension(Bas.Extensions.FooExt));
|
|
|
+var fooNoRegistry = Foo.Factory.ParseFrom(input);
|
|
|
+Assert.False(foo.HasExtension(Bas.Extensions.FooExt));
|
|
|
+```
|
|
|
+
|
|
|
+### Custom options
|
|
|
+
|
|
|
+The original `CustomOptions` APIs are now deprecated. Using the new generated extension identifiers,
|
|
|
+you can access extensions safely through the GetOption APIs. Note that cloneable values such as
|
|
|
+repeated fields and messages will be deep cloned.
|
|
|
+
|
|
|
+Example based on custom options usage example [here](https://github.com/protocolbuffers/protobuf/issues/5007#issuecomment-411604515).
|
|
|
+```cs
|
|
|
+foreach (var service in input.Services)
|
|
|
+{
|
|
|
+ Console.WriteLine($" {service.Name}");
|
|
|
+ foreach (var method in service.Methods)
|
|
|
+ {
|
|
|
+ var rule = method.GetOption(AnnotationsExtensions.Http);
|
|
|
+ if (rule != null)
|
|
|
+ {
|
|
|
+ Console.WriteLine($" {method.Name}: {rule}");
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ Console.WriteLine($" {method.Name}: no HTTP binding");
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+### Reflection
|
|
|
+
|
|
|
+Reflection APIs have been included to access the new portions of the library.
|
|
|
+
|
|
|
+ * FieldDescriptor.Extension
|
|
|
+ * Gets the extension identifier behind an extension field, allowing it to be added to an ExtensionRegistry
|
|
|
+ * FieldDescriptor.IsExtension
|
|
|
+ * Returns whether a field is an extension of another type.
|
|
|
+ * FieldDescriptor.ExtendeeType
|
|
|
+ * Returns the extended type of an extension field
|
|
|
+ * IFieldAccessor.HasValue
|
|
|
+ * Returns whether a field's value is set. For proto3 fields, throws an InvalidOperationException.
|
|
|
+ * FileDescriptor.Syntax
|
|
|
+ * Gets the syntax of a file
|
|
|
+ * FileDescriptor.Extensions
|
|
|
+ * An immutable list of extensions defined in the file
|
|
|
+ * MessageDescriptor.Extensions
|
|
|
+ * An immutable list of extensions defined in the message
|
|
|
+
|
|
|
+```cs
|
|
|
+var extensions = Baz.Descriptor.Extensions.GetExtensionsInDeclarationOrder(Foo.Descriptor);
|
|
|
+var registry = new ExtensionRegistry();
|
|
|
+registry.AddRange(extensions.Select(f => f.Extension));
|
|
|
+
|
|
|
+var baz = Foo.Descriptor.Parser.WithExtensionRegistry(registry).ParseFrom(input);
|
|
|
+foreach (var field in extensions)
|
|
|
+{
|
|
|
+ if (field.Accessor.HasValue(baz))
|
|
|
+ {
|
|
|
+ Console.WriteLine($"{field.Name}: {field.Accessor.GetValue(baz)}");
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|