protobuf/docs/csharp/proto2.md
2019-08-13 03:50:12 -05:00

6.5 KiB

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.

message Foo {
    optional Bar bar = 1;
    required Baz baz = 2;
}
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.

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.

option csharp_namespace = "FooBar";
extend Foo {
    optional Baz foo_ext = 124;
}
message Baz {
    extend Foo {
        repeated Baz repeated_foo_ext = 125;
    }
}
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 */;
    }
}
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.

message Baz {
    extend Foo {
        optional Baz foo_ext = 124;
    }
}
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.

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
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)}");
    }
}