1As part of the 3.10 release of Google.Protobuf, experimental proto2 support has been released. This document outlines the new changes brought about to include proto2 support. This does not break existing proto3 support and users may continue to use proto3 features without changing their current code. Again the generated code and public API associated with proto2 is experimental and subject to change in the future. APIs for proto2 may be added, removed, or adjusted as feedback is received. 2Generated code for proto2 may also be modified by adding, removing, or adjusting APIs as feedback is received. 3 4### Enabling proto2 features 5 6For information about specific proto2 features, please read the [proto2 language guide](https://developers.google.com/protocol-buffers/docs/proto). 7 8Much like other languages, proto2 features are used with proto2 files with the syntax declaration `syntax = "proto2";`. However, please note, proto3 is still the recommended version of protobuf and proto2 support is meant for legacy system interop and advanced uses. 9 10# Generated code 11 12### Messages 13 14Messages in proto2 files are very similar to their proto3 counterparts. They expose the usual property for getting and setting, but they also include properties and methods to handle field presence. 15 16For `optional`/`required` field XYZ, a `HasXYZ` property is included for checking presence and a `ClearXYZ` method is included for clearing the value. 17 18```proto 19message Foo { 20 optional Bar bar = 1; 21 required Baz baz = 2; 22} 23``` 24```cs 25var foo = new Foo(); 26Assert.IsNull(foo.Bar); 27Assert.False(foo.HasBar); 28foo.Bar = new Bar(); 29Assert.True(foo.HasBar); 30foo.ClearBar(); 31``` 32 33### Messages with extension ranges 34 35Messages which define extension ranges implement the `IExtendableMessage` interface as shown below. 36See inline comments for more info. 37 38```cs 39public interface IExtendableMessage<T> : IMessage<T> where T : IExtendableMessage<T> 40{ 41 // Gets the value of a single value extension. If the extension isn't present, this returns the default value. 42 TValue GetExtension<TValue>(Extension<T, TValue> extension); 43 // Gets the value of a repeated extension. If the extension hasn't been set, this returns null to prevent unnecessary allocations. 44 RepeatedField<TValue> GetExtension<TValue>(RepeatedExtension<T, TValue> extension); 45 // Gets the value of a repeated extension. This will initialize the value of the repeated field and will never return null. 46 RepeatedField<TValue> GetOrInitializeExtension<TValue>(RepeatedExtension<T, TValue> extension); 47 // Sets the value of the extension 48 void SetExtension<TValue>(Extension<T, TValue> extension, TValue value); 49 // Returns whether the extension is present in the message 50 bool HasExtension<TValue>(Extension<T, TValue> extension); 51 // Clears the value of the extension, removing it from the message 52 void ClearExtension<TValue>(Extension<T, TValue> extension); 53 // Clears the value of the repeated extension, removing it from the message. Calling GetExtension after this will always return null. 54 void ClearExtension<TValue>(RepeatedExtension<T, TValue> extension); 55} 56``` 57 58### Extensions 59 60Extensions are generated in static containers like reflection classes and type classes. 61For example for a file called `foo.proto` containing extensions in the file scope, a 62`FooExtensions` class is created containing the extensions defined in the file scope. 63For easy access, this class can be used with `using static` to bring all extensions into scope. 64 65```proto 66option csharp_namespace = "FooBar"; 67extend Foo { 68 optional Baz foo_ext = 124; 69} 70message Baz { 71 extend Foo { 72 repeated Baz repeated_foo_ext = 125; 73 } 74} 75``` 76```cs 77public static partial class FooExtensions { 78 public static readonly Extension<Foo, Baz> FooExt = /* initialization */; 79} 80 81public partial class Baz { 82 public partial static class Extensions { 83 public static readonly RepeatedExtension<Foo, Baz> RepeatedFooExt = /* initialization */; 84 } 85} 86``` 87```cs 88using static FooBar.FooExtensions; 89using static FooBar.Baz.Extensions; 90 91var foo = new Foo(); 92foo.SetExtension(FooExt, new Baz()); 93foo.GetOrInitializeExtension(RepeatedFooExt).Add(new Baz()); 94``` 95 96# APIs 97 98### Message initialization 99 100Initialization refers to checking the status of required fields in a proto2 message. If a message is uninitialized, not all required fields are set in either the message itself or any of its submessages. In other languages, missing required fields throw errors depending on the merge method used. This could cause unforseen errors at runtime if the incorrect method is used. 101However, in this implementation, 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. 102Checking message initialization can be done manually via the `IsInitialized` extension method in `MessageExtensions`. 103 104### Extension registries 105 106Just like in Java, extension registries can be constructed to parse extensions when reading new messages 107from input streams. The API is fairly similar to the Java API with some added bonuses with C# syntax sugars. 108 109```proto 110message Baz { 111 extend Foo { 112 optional Baz foo_ext = 124; 113 } 114} 115``` 116```cs 117var registry = new ExtensionRegistry() 118{ 119 Baz.Extensions.FooExt 120}; 121var foo = Foo.Factory.WithExtensionRegistry(registry).ParseFrom(input); 122Assert.True(foo.HasExtension(Bas.Extensions.FooExt)); 123var fooNoRegistry = Foo.Factory.ParseFrom(input); 124Assert.False(foo.HasExtension(Bas.Extensions.FooExt)); 125``` 126 127### Custom options 128 129Due to their limited use and lack of type safety, 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 130repeated fields and messages will be deep cloned. 131 132Example based on custom options usage example [here](https://github.com/protocolbuffers/protobuf/issues/5007#issuecomment-411604515). 133```cs 134foreach (var service in input.Services) 135{ 136 Console.WriteLine($" {service.Name}"); 137 foreach (var method in service.Methods) 138 { 139 var rule = method.GetOption(AnnotationsExtensions.Http); 140 if (rule != null) 141 { 142 Console.WriteLine($" {method.Name}: {rule}"); 143 } 144 else 145 { 146 Console.WriteLine($" {method.Name}: no HTTP binding"); 147 } 148 } 149} 150``` 151 152### Reflection 153 154Reflection APIs have been extended to enable accessing the new proto2 portions of the library and generated code. 155 156 * FieldDescriptor.Extension 157 * Gets the extension identifier behind an extension field, allowing it to be added to an ExtensionRegistry 158 * FieldDescriptor.IsExtension 159 * Returns whether a field is an extension of another type. 160 * FieldDescriptor.ExtendeeType 161 * Returns the extended type of an extension field 162 * IFieldAccessor.HasValue 163 * Returns whether a field's value is set. For proto3 fields, throws an InvalidOperationException. 164 * FileDescriptor.Syntax 165 * Gets the syntax of a file 166 * FileDescriptor.Extensions 167 * An immutable list of extensions defined in the file 168 * MessageDescriptor.Extensions 169 * An immutable list of extensions defined in the message 170 171```cs 172var extensions = Baz.Descriptor.Extensions.GetExtensionsInDeclarationOrder(Foo.Descriptor); 173var registry = new ExtensionRegistry(); 174registry.AddRange(extensions.Select(f => f.Extension)); 175 176var baz = Foo.Descriptor.Parser.WithExtensionRegistry(registry).ParseFrom(input); 177foreach (var field in extensions) 178{ 179 if (field.Accessor.HasValue(baz)) 180 { 181 Console.WriteLine($"{field.Name}: {field.Accessor.GetValue(baz)}"); 182 } 183} 184``` 185