184 lines
6.5 KiB
Markdown
184 lines
6.5 KiB
Markdown
|
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)}");
|
||
|
}
|
||
|
}
|
||
|
```
|