parent
c132a4aa16
commit
54a3d8e7ef
1 changed files with 184 additions and 0 deletions
@ -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)}"); |
||||
} |
||||
} |
||||
``` |
Loading…
Reference in new issue