Explorar el Código

Support custom options in C#

This consists of:
- Changing the codegen for the fixed set of options protos, to parse unknown fields instead of skipping them
- Add a new CustomOptions type in the C# support library
- Expose CustomOptions properties from the immutable proto wrappers in the support library

Only single-value options are currently supported, and fetching options values requires getting the type right
and knowing the field number. Both of these can be addressed at a later time.

Fixes #2143, at least as a first pass.
Jon Skeet hace 8 años
padre
commit
047575f20c

+ 4 - 0
Makefile.am

@@ -60,6 +60,7 @@ csharp_EXTRA_DIST=                                                           \
   csharp/keys/Google.Protobuf.public.snk                                     \
   csharp/keys/Google.Protobuf.public.snk                                     \
   csharp/keys/Google.Protobuf.snk                                            \
   csharp/keys/Google.Protobuf.snk                                            \
   csharp/keys/README.md                                                      \
   csharp/keys/README.md                                                      \
+  csharp/protos/unittest_custom_options_proto3.proto                         \
   csharp/protos/unittest_issues.proto                                        \
   csharp/protos/unittest_issues.proto                                        \
   csharp/src/AddressBook/AddPerson.cs                                        \
   csharp/src/AddressBook/AddPerson.cs                                        \
   csharp/src/AddressBook/Addressbook.cs                                      \
   csharp/src/AddressBook/Addressbook.cs                                      \
@@ -92,6 +93,7 @@ csharp_EXTRA_DIST=                                                           \
   csharp/src/Google.Protobuf.Test/JsonFormatterTest.cs                       \
   csharp/src/Google.Protobuf.Test/JsonFormatterTest.cs                       \
   csharp/src/Google.Protobuf.Test/JsonParserTest.cs                          \
   csharp/src/Google.Protobuf.Test/JsonParserTest.cs                          \
   csharp/src/Google.Protobuf.Test/JsonTokenizerTest.cs                       \
   csharp/src/Google.Protobuf.Test/JsonTokenizerTest.cs                       \
+  csharp/src/Google.Protobuf.Test/Reflection/CustomOptionsTest.cs            \
   csharp/src/Google.Protobuf.Test/Reflection/DescriptorsTest.cs              \
   csharp/src/Google.Protobuf.Test/Reflection/DescriptorsTest.cs              \
   csharp/src/Google.Protobuf.Test/Reflection/FieldAccessTest.cs              \
   csharp/src/Google.Protobuf.Test/Reflection/FieldAccessTest.cs              \
   csharp/src/Google.Protobuf.Test/Reflection/TypeRegistryTest.cs             \
   csharp/src/Google.Protobuf.Test/Reflection/TypeRegistryTest.cs             \
@@ -101,6 +103,7 @@ csharp_EXTRA_DIST=                                                           \
   csharp/src/Google.Protobuf.Test/TestProtos/ForeignMessagePartial.cs        \
   csharp/src/Google.Protobuf.Test/TestProtos/ForeignMessagePartial.cs        \
   csharp/src/Google.Protobuf.Test/TestProtos/MapUnittestProto3.cs            \
   csharp/src/Google.Protobuf.Test/TestProtos/MapUnittestProto3.cs            \
   csharp/src/Google.Protobuf.Test/TestProtos/TestMessagesProto3.cs           \
   csharp/src/Google.Protobuf.Test/TestProtos/TestMessagesProto3.cs           \
+  csharp/src/Google.Protobuf.Test/TestProtos/UnittestCustomOptionsProto3.cs  \
   csharp/src/Google.Protobuf.Test/TestProtos/UnittestImportProto3.cs         \
   csharp/src/Google.Protobuf.Test/TestProtos/UnittestImportProto3.cs         \
   csharp/src/Google.Protobuf.Test/TestProtos/UnittestImportPublicProto3.cs   \
   csharp/src/Google.Protobuf.Test/TestProtos/UnittestImportPublicProto3.cs   \
   csharp/src/Google.Protobuf.Test/TestProtos/UnittestIssues.cs               \
   csharp/src/Google.Protobuf.Test/TestProtos/UnittestIssues.cs               \
@@ -140,6 +143,7 @@ csharp_EXTRA_DIST=                                                           \
   csharp/src/Google.Protobuf/MessageParser.cs                                \
   csharp/src/Google.Protobuf/MessageParser.cs                                \
   csharp/src/Google.Protobuf/ProtoPreconditions.cs                           \
   csharp/src/Google.Protobuf/ProtoPreconditions.cs                           \
   csharp/src/Google.Protobuf/Properties/AssemblyInfo.cs                      \
   csharp/src/Google.Protobuf/Properties/AssemblyInfo.cs                      \
+  csharp/src/Google.Protobuf/Reflection/CustomOptions.cs                     \
   csharp/src/Google.Protobuf/Reflection/Descriptor.cs                        \
   csharp/src/Google.Protobuf/Reflection/Descriptor.cs                        \
   csharp/src/Google.Protobuf/Reflection/DescriptorBase.cs                    \
   csharp/src/Google.Protobuf/Reflection/DescriptorBase.cs                    \
   csharp/src/Google.Protobuf/Reflection/DescriptorPool.cs                    \
   csharp/src/Google.Protobuf/Reflection/DescriptorPool.cs                    \

+ 3 - 2
csharp/generate_protos.sh

@@ -50,9 +50,10 @@ $PROTOC -Isrc --csharp_out=csharp/src/Google.Protobuf.Test \
     src/google/protobuf/unittest_well_known_types.proto
     src/google/protobuf/unittest_well_known_types.proto
 
 
 # Different base namespace to the protos above
 # Different base namespace to the protos above
-$PROTOC -Icsharp/protos --csharp_out=csharp/src/Google.Protobuf.Test \
+$PROTOC -Isrc -Icsharp/protos --csharp_out=csharp/src/Google.Protobuf.Test \
     --csharp_opt=base_namespace=UnitTest.Issues \
     --csharp_opt=base_namespace=UnitTest.Issues \
-    csharp/protos/unittest_issues.proto
+    csharp/protos/unittest_issues.proto \
+    csharp/protos/unittest_custom_options_proto3.proto
 
 
 # Don't specify a base namespace at all; we just want to make sure the
 # Don't specify a base namespace at all; we just want to make sure the
 # results end up in TestProtos.
 # results end up in TestProtos.

+ 338 - 0
csharp/protos/unittest_custom_options_proto3.proto

@@ -0,0 +1,338 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// Author: benjy@google.com (Benjy Weinberger)
+//  Based on original Protocol Buffers design by
+//  Sanjay Ghemawat, Jeff Dean, and others.
+//
+// A proto file used to test the "custom options" feature of google.protobuf.
+
+// This file is based on unittest_custom_options.proto in
+// src/google/protobuf, but is modified for proto3. It could
+// potentially be moved into src/google/protobuf, but currently C#
+// is the only language that really needs it, as we don't support
+// proto2 syntax. It's cut down significantly as proto3 only supports
+// extensions for options.
+
+
+syntax = "proto3";
+
+// A custom file option (defined below).
+option (file_opt1) = 9876543210;
+
+import "google/protobuf/descriptor.proto";
+
+// We don't put this in a package within proto2 because we need to make sure
+// that the generated code doesn't depend on being in the proto2 namespace.
+package protobuf_unittest;
+option csharp_namespace = "UnitTest.Issues.TestProtos";
+
+// Some simple test custom options of various types.
+
+extend google.protobuf.FileOptions {
+  uint64 file_opt1 = 7736974;
+}
+
+extend google.protobuf.MessageOptions {
+  int32 message_opt1 = 7739036;
+}
+
+extend google.protobuf.FieldOptions {
+  fixed64 field_opt1 = 7740936;
+}
+
+// See https://github.com/google/protobuf/issues/2316
+// extend google.protobuf.OneofOptions {
+//   int32 oneof_opt1 = 7740111;
+// }
+
+extend google.protobuf.EnumOptions {
+  sfixed32 enum_opt1 = 7753576;
+}
+
+extend google.protobuf.EnumValueOptions {
+  int32 enum_value_opt1 = 1560678;
+}
+
+extend google.protobuf.ServiceOptions {
+  sint64 service_opt1 = 7887650;
+}
+
+enum MethodOpt1 {
+  METHODOPT1_UNSPECIFIED = 0;
+  METHODOPT1_VAL1 = 1;
+  METHODOPT1_VAL2 = 2;
+}
+
+extend google.protobuf.MethodOptions {
+  MethodOpt1 method_opt1 = 7890860;
+}
+
+// A test message with custom options at all possible locations (and also some
+// regular options, to make sure they interact nicely).
+message TestMessageWithCustomOptions {
+  option message_set_wire_format = false;
+
+  option (message_opt1) = -56;
+
+  string field1 = 1 [ctype=CORD,
+                              (field_opt1)=8765432109];
+
+  oneof AnOneof {
+// See https://github.com/google/protobuf/issues/2316
+//    option (oneof_opt1) = -99;
+    int32 oneof_field = 2;
+  }
+
+  enum AnEnum {
+    option (enum_opt1) = -789;
+    ANENUM_UNSPECIFIED = 0;
+    ANENUM_VAL1 = 1;
+    ANENUM_VAL2 = 2 [(enum_value_opt1) = 123];
+  }
+}
+
+
+// A test RPC service with custom options at all possible locations (and also
+// some regular options, to make sure they interact nicely).
+message CustomOptionFooRequest {
+}
+
+message CustomOptionFooResponse {
+}
+
+message CustomOptionFooClientMessage {
+}
+
+message CustomOptionFooServerMessage {
+}
+
+service TestServiceWithCustomOptions {
+  option (service_opt1) = -9876543210;
+
+  rpc Foo(CustomOptionFooRequest) returns (CustomOptionFooResponse) {
+    option (method_opt1) = METHODOPT1_VAL2;
+  }
+}
+
+
+
+// Options of every possible field type, so we can test them all exhaustively.
+
+message DummyMessageContainingEnum {
+  enum TestEnumType {
+    TEST_OPTION_ENUM_UNSPECIFIED = 0;
+    TEST_OPTION_ENUM_TYPE1 = 22;
+    TEST_OPTION_ENUM_TYPE2 = -23;
+  }
+}
+
+message DummyMessageInvalidAsOptionType {
+}
+
+extend google.protobuf.MessageOptions {
+          bool     bool_opt = 7706090;
+         int32    int32_opt = 7705709;
+         int64    int64_opt = 7705542;
+        uint32   uint32_opt = 7704880;
+        uint64   uint64_opt = 7702367;
+        sint32   sint32_opt = 7701568;
+        sint64   sint64_opt = 7700863;
+       fixed32  fixed32_opt = 7700307;
+       fixed64  fixed64_opt = 7700194;
+      sfixed32 sfixed32_opt = 7698645;
+      sfixed64 sfixed64_opt = 7685475;
+         float    float_opt = 7675390;
+        double   double_opt = 7673293;
+        string   string_opt = 7673285;
+         bytes    bytes_opt = 7673238;
+  DummyMessageContainingEnum.TestEnumType enum_opt = 7673233;
+  DummyMessageInvalidAsOptionType message_type_opt = 7665967;
+}
+
+message CustomOptionMinIntegerValues {
+  option     (bool_opt) = false;
+  option    (int32_opt) = -0x80000000;
+  option    (int64_opt) = -0x8000000000000000;
+  option   (uint32_opt) = 0;
+  option   (uint64_opt) = 0;
+  option   (sint32_opt) = -0x80000000;
+  option   (sint64_opt) = -0x8000000000000000;
+  option  (fixed32_opt) = 0;
+  option  (fixed64_opt) = 0;
+  option (sfixed32_opt) = -0x80000000;
+  option (sfixed64_opt) = -0x8000000000000000;
+}
+
+message CustomOptionMaxIntegerValues {
+  option     (bool_opt) = true;
+  option    (int32_opt) = 0x7FFFFFFF;
+  option    (int64_opt) = 0x7FFFFFFFFFFFFFFF;
+  option   (uint32_opt) = 0xFFFFFFFF;
+  option   (uint64_opt) = 0xFFFFFFFFFFFFFFFF;
+  option   (sint32_opt) = 0x7FFFFFFF;
+  option   (sint64_opt) = 0x7FFFFFFFFFFFFFFF;
+  option  (fixed32_opt) = 0xFFFFFFFF;
+  option  (fixed64_opt) = 0xFFFFFFFFFFFFFFFF;
+  option (sfixed32_opt) = 0x7FFFFFFF;
+  option (sfixed64_opt) = 0x7FFFFFFFFFFFFFFF;
+}
+
+message CustomOptionOtherValues {
+  option  (int32_opt) = -100;  // To test sign-extension.
+  option  (float_opt) = 12.3456789;
+  option (double_opt) = 1.234567890123456789;
+  option (string_opt) = "Hello, \"World\"";
+  option  (bytes_opt) = "Hello\0World";
+  option   (enum_opt) = TEST_OPTION_ENUM_TYPE2;
+}
+
+message SettingRealsFromPositiveInts {
+  option  (float_opt) = 12;
+  option (double_opt) = 154;
+}
+
+message SettingRealsFromNegativeInts {
+  option  (float_opt) = -12;
+  option  (double_opt) = -154;
+}
+
+// Options of complex message types, themselves combined and extended in
+// various ways.
+
+message ComplexOptionType1 {
+  int32 foo = 1;
+  int32 foo2 = 2;
+  int32 foo3 = 3;
+  repeated int32 foo4 = 4;
+}
+
+message ComplexOptionType2 {
+  ComplexOptionType1 bar = 1;
+  int32 baz = 2;
+
+  message ComplexOptionType4 {
+    int32 waldo = 1;
+
+    extend google.protobuf.MessageOptions {
+      ComplexOptionType4 complex_opt4 = 7633546;
+    }
+  }
+
+  ComplexOptionType4 fred = 3;
+  repeated ComplexOptionType4 barney = 4;
+}
+
+message ComplexOptionType3 {
+  int32 qux = 1;
+}
+
+extend google.protobuf.MessageOptions {
+  protobuf_unittest.ComplexOptionType1 complex_opt1 = 7646756;
+  ComplexOptionType2 complex_opt2 = 7636949;
+  ComplexOptionType3 complex_opt3 = 7636463;
+}
+
+// Note that we try various different ways of naming the same extension.
+message VariousComplexOptions {
+  option (.protobuf_unittest.complex_opt1).foo = 42;
+  option (protobuf_unittest.complex_opt1).foo4 = 99;
+  option (protobuf_unittest.complex_opt1).foo4 = 88;
+  option (complex_opt2).baz = 987;
+  option (complex_opt2).bar.foo = 743;
+  option (ComplexOptionType2.ComplexOptionType4.complex_opt4).waldo = 1971;
+  option (complex_opt2).fred.waldo = 321;
+  option (complex_opt2).barney = { waldo: 101 };
+  option (complex_opt2).barney = { waldo: 212 };
+  option (protobuf_unittest.complex_opt3).qux = 9;
+}
+
+// ------------------------------------------------------
+// Definitions for testing aggregate option parsing.
+// See descriptor_unittest.cc.
+
+// A helper type used to test aggregate option parsing
+message Aggregate {
+  int32 i = 1;
+  string s = 2;
+
+  // A nested object
+  Aggregate sub = 3;
+}
+
+// Allow Aggregate to be used as an option at all possible locations
+// in the .proto grammer.
+extend google.protobuf.FileOptions      { Aggregate fileopt    = 15478479; }
+extend google.protobuf.MessageOptions   { Aggregate msgopt     = 15480088; }
+extend google.protobuf.FieldOptions     { Aggregate fieldopt   = 15481374; }
+extend google.protobuf.EnumOptions      { Aggregate enumopt    = 15483218; }
+extend google.protobuf.EnumValueOptions { Aggregate enumvalopt = 15486921; }
+extend google.protobuf.ServiceOptions   { Aggregate serviceopt = 15497145; }
+extend google.protobuf.MethodOptions    { Aggregate methodopt  = 15512713; }
+
+// Try using AggregateOption at different points in the proto grammar
+option (fileopt) = {
+  s: 'FileAnnotation'
+  // Also test the handling of comments
+  /* of both types */ i: 100
+
+  sub { s: 'NestedFileAnnotation' }
+};
+
+message AggregateMessage {
+  option (msgopt) = { i:101 s:'MessageAnnotation' };
+  int32 fieldname = 1 [(fieldopt) = { s:'FieldAnnotation' }];
+}
+
+service AggregateService {
+  option (serviceopt) = { s:'ServiceAnnotation' };
+  rpc Method (AggregateMessage) returns (AggregateMessage) {
+    option (methodopt) = { s:'MethodAnnotation' };
+  }
+}
+
+enum AggregateEnum {
+  option (enumopt) = { s:'EnumAnnotation' };
+  UNSPECIFIED = 0;
+  VALUE = 1 [(enumvalopt) = { s:'EnumValueAnnotation' }];
+}
+
+// Test custom options for nested type.
+message NestedOptionType {
+  message NestedMessage {
+    option (message_opt1) = 1001;
+    int32 nested_field = 1 [(field_opt1) = 1002];
+  }
+  enum NestedEnum {
+    UNSPECIFIED = 0;
+    option (enum_opt1) = 1003;
+    NESTED_ENUM_VALUE = 1 [(enum_value_opt1) = 1004];
+  }
+}

+ 267 - 0
csharp/src/Google.Protobuf.Test/Reflection/CustomOptionsTest.cs

@@ -0,0 +1,267 @@
+#region Copyright notice and license
+// Protocol Buffers - Google's data interchange format
+// Copyright 2017 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#endregion
+
+using Google.Protobuf.Reflection;
+using Google.Protobuf.WellKnownTypes;
+using NUnit.Framework;
+using System.IO;
+using System.Linq;
+using UnitTest.Issues.TestProtos;
+using static Google.Protobuf.WireFormat;
+using static UnitTest.Issues.TestProtos.ComplexOptionType2.Types;
+using static UnitTest.Issues.TestProtos.DummyMessageContainingEnum.Types;
+using static Google.Protobuf.Test.Reflection.CustomOptionNumber;
+
+namespace Google.Protobuf.Test.Reflection
+{
+    // Internal enum to allow us to use "using static" for convenience.
+    // These are the options defined in unittest_custom_options_proto3.proto
+    internal enum CustomOptionNumber
+    {
+        FileOpt1 = 7736974,
+        MessageOpt1 = 7739036,
+        FieldOpt1 = 7740936,
+        EnumOpt1 = 7753576,
+        EnumValueOpt1 = 1560678,
+        ServiceOpt1 = 7887650,
+        MethodOpt1 = 7890860,
+
+        // All message options...
+        BoolOpt = 7706090,
+        Int32Opt = 7705709,
+        Int64Opt = 7705542,
+        UInt32Opt = 7704880,
+        UInt64Opt = 7702367,
+        SInt32Opt = 7701568,
+        SInt64Opt = 7700863,
+        Fixed32Opt = 7700307,
+        Fixed64Opt = 7700194,
+        SFixed32Opt = 7698645,
+        SFixed64Opt = 7685475,
+        FloatOpt = 7675390,
+        DoubleOpt = 7673293,
+        StringOpt = 7673285,
+        BytesOpt = 7673238,
+        EnumOpt = 7673233,
+        MessageTypeOpt = 7665967,
+
+        // Miscellaneous
+        ComplexOpt4 = 7633546,
+        ComplexOpt1 = 7646756,
+        ComplexOpt2 = 7636949,
+        ComplexOpt3 = 7636463,
+
+        // Aggregates
+        AggregateFileOpt = 15478479,
+        AggregateMsgOpt = 15480088,
+        AggregateFieldOpt = 15481374,
+        AggregateEnumOpt = 15483218,
+        AggregateEnumValueOpt = 15486921,
+        AggregateServiceOpt = 15497145,
+        AggregateMethodOpt = 15512713,
+    }
+
+    /// <summary>
+    /// The majority of the testing here is done via parsed descriptors. That's simpler to
+    /// achieve (and more important) than constructing a CodedInputStream manually.
+    /// </summary>
+    public class CustomOptionsTest
+    {
+        delegate bool OptionFetcher<T>(int field, out T value);
+
+        [Test]
+        public void EmptyOptionsIsShared()
+        {
+            var structOptions = Struct.Descriptor.CustomOptions;
+            var timestampOptions = Struct.Descriptor.CustomOptions;
+            Assert.AreSame(structOptions, timestampOptions);
+        }
+
+        [Test]
+        public void SimpleIntegerTest()
+        {
+            var stream = new MemoryStream();
+            var output = new CodedOutputStream(stream);
+            output.WriteTag(MakeTag(1, WireType.Varint));
+            output.WriteInt32(1234567);
+            output.Flush();
+            stream.Position = 0;
+            var input = new CodedInputStream(stream);
+            input.ReadTag();
+
+            var options = CustomOptions.Empty;
+            options = options.ReadOrSkipUnknownField(input);
+
+            int intValue;
+            Assert.True(options.TryGetInt32(1, out intValue));
+            Assert.AreEqual(1234567, intValue);
+
+            string stringValue;
+            // No ByteString stored values
+            Assert.False(options.TryGetString(1, out stringValue));
+            // Nothing stored for field 2
+            Assert.False(options.TryGetInt32(2, out intValue));
+        }
+
+        [Test]
+        public void SimpleStringTest()
+        {
+            var stream = new MemoryStream();
+            var output = new CodedOutputStream(stream);
+            output.WriteTag(MakeTag(1, WireType.LengthDelimited));
+            output.WriteString("value");
+            output.Flush();
+            stream.Position = 0;
+            var input = new CodedInputStream(stream);
+            input.ReadTag();
+
+            var options = CustomOptions.Empty;
+            options = options.ReadOrSkipUnknownField(input);
+
+            string stringValue;
+            Assert.True(options.TryGetString(1, out stringValue));
+            Assert.AreEqual("value", stringValue);
+
+            int intValue;
+            // No numeric stored values
+            Assert.False(options.TryGetInt32(1, out intValue));
+            // Nothing stored for field 2
+            Assert.False(options.TryGetString(2, out stringValue));
+        }
+
+        [Test]
+        public void ScalarOptions()
+        {
+            var options = CustomOptionOtherValues.Descriptor.CustomOptions;
+            AssertOption(-100, options.TryGetInt32, Int32Opt);
+            AssertOption(12.3456789f, options.TryGetFloat, FloatOpt);
+            AssertOption(1.234567890123456789d, options.TryGetDouble, DoubleOpt);
+            AssertOption("Hello, \"World\"", options.TryGetString, StringOpt);
+            AssertOption(ByteString.CopyFromUtf8("Hello\0World"), options.TryGetBytes, BytesOpt);
+            AssertOption((int) TestEnumType.TestOptionEnumType2, options.TryGetInt32, EnumOpt);
+        }
+
+        [Test]
+        public void MessageOptions()
+        {
+            var options = VariousComplexOptions.Descriptor.CustomOptions;
+            AssertOption(new ComplexOptionType1 { Foo = 42, Foo4 = { 99, 88 } }, options.TryGetMessage, ComplexOpt1);
+            AssertOption(new ComplexOptionType2
+                {
+                    Baz = 987, Bar = new ComplexOptionType1 { Foo = 743 },
+                    Fred = new ComplexOptionType4 { Waldo = 321 },
+                    Barney = { new ComplexOptionType4 { Waldo = 101 }, new ComplexOptionType4 { Waldo = 212 } }
+                },
+                options.TryGetMessage, ComplexOpt2);
+            AssertOption(new ComplexOptionType3 { Qux = 9 }, options.TryGetMessage, ComplexOpt3);
+        }
+
+        [Test]
+        public void OptionLocations()
+        {
+            var fileOptions = UnittestCustomOptionsProto3Reflection.Descriptor.CustomOptions;
+            AssertOption(9876543210UL, fileOptions.TryGetUInt64, FileOpt1);
+
+            var messageOptions = TestMessageWithCustomOptions.Descriptor.CustomOptions;
+            AssertOption(-56, messageOptions.TryGetInt32, MessageOpt1);
+
+            var fieldOptions = TestMessageWithCustomOptions.Descriptor.Fields["field1"] .CustomOptions;
+            AssertOption(8765432109UL, fieldOptions.TryGetFixed64, FieldOpt1);
+
+            var enumOptions = TestMessageWithCustomOptions.Descriptor.EnumTypes[0].CustomOptions;
+            AssertOption(-789, enumOptions.TryGetSFixed32, EnumOpt1);
+
+            var enumValueOptions = TestMessageWithCustomOptions.Descriptor.EnumTypes[0].FindValueByNumber(2).CustomOptions;
+            AssertOption(123, enumValueOptions.TryGetInt32, EnumValueOpt1);
+
+            var service = UnittestCustomOptionsProto3Reflection.Descriptor.Services
+                .Single(s => s.Name == "TestServiceWithCustomOptions");
+            var serviceOptions = service.CustomOptions;
+            AssertOption(-9876543210, serviceOptions.TryGetSInt64, ServiceOpt1);
+
+            var methodOptions = service.Methods[0].CustomOptions;
+            AssertOption((int) UnitTest.Issues.TestProtos.MethodOpt1.Val2, methodOptions.TryGetInt32, CustomOptionNumber.MethodOpt1);
+        }
+
+        [Test]
+        public void MinValues()
+        {
+            var options = CustomOptionMinIntegerValues.Descriptor.CustomOptions;
+            AssertOption(false, options.TryGetBool, BoolOpt);
+            AssertOption(int.MinValue, options.TryGetInt32, Int32Opt);
+            AssertOption(long.MinValue, options.TryGetInt64, Int64Opt);
+            AssertOption(uint.MinValue, options.TryGetUInt32, UInt32Opt);
+            AssertOption(ulong.MinValue, options.TryGetUInt64, UInt64Opt);
+            AssertOption(int.MinValue, options.TryGetSInt32, SInt32Opt);
+            AssertOption(long.MinValue, options.TryGetSInt64, SInt64Opt);
+            AssertOption(uint.MinValue, options.TryGetUInt32, Fixed32Opt);
+            AssertOption(ulong.MinValue, options.TryGetUInt64, Fixed64Opt);
+            AssertOption(int.MinValue, options.TryGetInt32, SFixed32Opt);
+            AssertOption(long.MinValue, options.TryGetInt64, SFixed64Opt);
+        }
+
+        [Test]
+        public void MaxValues()
+        {
+            var options = CustomOptionMaxIntegerValues.Descriptor.CustomOptions;
+            AssertOption(true, options.TryGetBool, BoolOpt);
+            AssertOption(int.MaxValue, options.TryGetInt32, Int32Opt);
+            AssertOption(long.MaxValue, options.TryGetInt64, Int64Opt);
+            AssertOption(uint.MaxValue, options.TryGetUInt32, UInt32Opt);
+            AssertOption(ulong.MaxValue, options.TryGetUInt64, UInt64Opt);
+            AssertOption(int.MaxValue, options.TryGetSInt32, SInt32Opt);
+            AssertOption(long.MaxValue, options.TryGetSInt64, SInt64Opt);
+            AssertOption(uint.MaxValue, options.TryGetFixed32, Fixed32Opt);
+            AssertOption(ulong.MaxValue, options.TryGetFixed64, Fixed64Opt);
+            AssertOption(int.MaxValue, options.TryGetSFixed32, SFixed32Opt);
+            AssertOption(long.MaxValue, options.TryGetSFixed64, SFixed64Opt);
+        }
+
+        [Test]
+        public void AggregateOptions()
+        {
+            // Just two examples
+            var messageOptions = AggregateMessage.Descriptor.CustomOptions;
+            AssertOption(new Aggregate { I = 101, S = "MessageAnnotation" }, messageOptions.TryGetMessage, AggregateMsgOpt);
+
+            var fieldOptions = AggregateMessage.Descriptor.Fields["fieldname"].CustomOptions;
+            AssertOption(new Aggregate { S = "FieldAnnotation" }, fieldOptions.TryGetMessage, AggregateFieldOpt);
+        }
+
+        private void AssertOption<T>(T expected, OptionFetcher<T> fetcher, CustomOptionNumber field)
+        {
+            T actual;
+            Assert.IsTrue(fetcher((int) field, out actual));
+            Assert.AreEqual(expected, actual);
+        }
+    }
+}

+ 2627 - 0
csharp/src/Google.Protobuf.Test/TestProtos/UnittestCustomOptionsProto3.cs

@@ -0,0 +1,2627 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: unittest_custom_options_proto3.proto
+#pragma warning disable 1591, 0612, 3021
+#region Designer generated code
+
+using pb = global::Google.Protobuf;
+using pbc = global::Google.Protobuf.Collections;
+using pbr = global::Google.Protobuf.Reflection;
+using scg = global::System.Collections.Generic;
+namespace UnitTest.Issues.TestProtos {
+
+  /// <summary>Holder for reflection information generated from unittest_custom_options_proto3.proto</summary>
+  public static partial class UnittestCustomOptionsProto3Reflection {
+
+    #region Descriptor
+    /// <summary>File descriptor for unittest_custom_options_proto3.proto</summary>
+    public static pbr::FileDescriptor Descriptor {
+      get { return descriptor; }
+    }
+    private static pbr::FileDescriptor descriptor;
+
+    static UnittestCustomOptionsProto3Reflection() {
+      byte[] descriptorData = global::System.Convert.FromBase64String(
+          string.Concat(
+            "CiR1bml0dGVzdF9jdXN0b21fb3B0aW9uc19wcm90bzMucHJvdG8SEXByb3Rv",
+            "YnVmX3VuaXR0ZXN0GiBnb29nbGUvcHJvdG9idWYvZGVzY3JpcHRvci5wcm90",
+            "byLHAQocVGVzdE1lc3NhZ2VXaXRoQ3VzdG9tT3B0aW9ucxIeCgZmaWVsZDEY",
+            "ASABKAlCDggBweDDHS3hdQoCAAAAEhUKC29uZW9mX2ZpZWxkGAIgASgFSAAi",
+            "UwoGQW5FbnVtEhYKEkFORU5VTV9VTlNQRUNJRklFRBAAEg8KC0FORU5VTV9W",
+            "QUwxEAESFgoLQU5FTlVNX1ZBTDIQAhoFsIb6BXsaCMX2yR3r/P//OhAIAODp",
+            "wh3I//////////8BQgkKB0FuT25lb2YiGAoWQ3VzdG9tT3B0aW9uRm9vUmVx",
+            "dWVzdCIZChdDdXN0b21PcHRpb25Gb29SZXNwb25zZSIeChxDdXN0b21PcHRp",
+            "b25Gb29DbGllbnRNZXNzYWdlIh4KHEN1c3RvbU9wdGlvbkZvb1NlcnZlck1l",
+            "c3NhZ2UijwEKGkR1bW15TWVzc2FnZUNvbnRhaW5pbmdFbnVtInEKDFRlc3RF",
+            "bnVtVHlwZRIgChxURVNUX09QVElPTl9FTlVNX1VOU1BFQ0lGSUVEEAASGgoW",
+            "VEVTVF9PUFRJT05fRU5VTV9UWVBFMRAWEiMKFlRFU1RfT1BUSU9OX0VOVU1f",
+            "VFlQRTIQ6f//////////ASIhCh9EdW1teU1lc3NhZ2VJbnZhbGlkQXNPcHRp",
+            "b25UeXBlIooBChxDdXN0b21PcHRpb25NaW5JbnRlZ2VyVmFsdWVzOmrQ3rId",
+            "AOjGsh2AgICA+P////8BsLyyHYCAgICAgICAgAGAk7IdAPj1sB0AgMSwHf//",
+            "//8P+JewHf///////////wGd9a8dAAAAAJHurx0AAAAAAAAAAK2Nrx0AAACA",
+            "mdaoHQAAAAAAAACAIpEBChxDdXN0b21PcHRpb25NYXhJbnRlZ2VyVmFsdWVz",
+            "OnHQ3rIdAejGsh3/////B7C8sh3//////////3+Ak7Id/////w/49bAd////",
+            "////////AYDEsB3+////D/iXsB3+//////////8BnfWvHf////+R7q8d////",
+            "//////+tja8d////f5nWqB3/////////fyJuChdDdXN0b21PcHRpb25PdGhl",
+            "clZhbHVlczpT6MayHZz//////////wH136Md54dFQencoh37WYxCysDzP6rc",
+            "oh0OSGVsbG8sICJXb3JsZCKy2aIdC0hlbGxvAFdvcmxkiNmiHen/////////",
+            "/wEiNAocU2V0dGluZ1JlYWxzRnJvbVBvc2l0aXZlSW50czoU9d+jHQAAQEHp",
+            "3KIdAAAAAABAY0AiNAocU2V0dGluZ1JlYWxzRnJvbU5lZ2F0aXZlSW50czoU",
+            "9d+jHQAAQMHp3KIdAAAAAABAY8AiSwoSQ29tcGxleE9wdGlvblR5cGUxEgsK",
+            "A2ZvbxgBIAEoBRIMCgRmb28yGAIgASgFEgwKBGZvbzMYAyABKAUSDAoEZm9v",
+            "NBgEIAMoBSKBAwoSQ29tcGxleE9wdGlvblR5cGUyEjIKA2JhchgBIAEoCzIl",
+            "LnByb3RvYnVmX3VuaXR0ZXN0LkNvbXBsZXhPcHRpb25UeXBlMRILCgNiYXoY",
+            "AiABKAUSRgoEZnJlZBgDIAEoCzI4LnByb3RvYnVmX3VuaXR0ZXN0LkNvbXBs",
+            "ZXhPcHRpb25UeXBlMi5Db21wbGV4T3B0aW9uVHlwZTQSSAoGYmFybmV5GAQg",
+            "AygLMjgucHJvdG9idWZfdW5pdHRlc3QuQ29tcGxleE9wdGlvblR5cGUyLkNv",
+            "bXBsZXhPcHRpb25UeXBlNBqXAQoSQ29tcGxleE9wdGlvblR5cGU0Eg0KBXdh",
+            "bGRvGAEgASgFMnIKDGNvbXBsZXhfb3B0NBIfLmdvb2dsZS5wcm90b2J1Zi5N",
+            "ZXNzYWdlT3B0aW9ucxiK9dEDIAEoCzI4LnByb3RvYnVmX3VuaXR0ZXN0LkNv",
+            "bXBsZXhPcHRpb25UeXBlMi5Db21wbGV4T3B0aW9uVHlwZTQiIQoSQ29tcGxl",
+            "eE9wdGlvblR5cGUzEgsKA3F1eBgBIAEoBSJsChVWYXJpb3VzQ29tcGxleE9w",
+            "dGlvbnM6U6LilR0CCCqi4pUdAiBjouKVHQIgWKr9kB0DENsHqv2QHQUKAwjn",
+            "BdKojx0DCLMPqv2QHQUaAwjBAqr9kB0EIgIIZar9kB0FIgMI1AH63pAdAggJ",
+            "IkwKCUFnZ3JlZ2F0ZRIJCgFpGAEgASgFEgkKAXMYAiABKAkSKQoDc3ViGAMg",
+            "ASgLMhwucHJvdG9idWZfdW5pdHRlc3QuQWdncmVnYXRlIlkKEEFnZ3JlZ2F0",
+            "ZU1lc3NhZ2USKQoJZmllbGRuYW1lGAEgASgFQhbyoYc7ERIPRmllbGRBbm5v",
+            "dGF0aW9uOhrC0YY7FQhlEhFNZXNzYWdlQW5ub3RhdGlvbiKXAQoQTmVzdGVk",
+            "T3B0aW9uVHlwZRo7Cg1OZXN0ZWRNZXNzYWdlEiIKDG5lc3RlZF9maWVsZBgB",
+            "IAEoBUIMweDDHeoDAAAAAAAAOgbg6cId6QciRgoKTmVzdGVkRW51bRIPCgtV",
+            "TlNQRUNJRklFRBAAEh0KEU5FU1RFRF9FTlVNX1ZBTFVFEAEaBrCG+gXsBxoI",
+            "xfbJHesDAAAqUgoKTWV0aG9kT3B0MRIaChZNRVRIT0RPUFQxX1VOU1BFQ0lG",
+            "SUVEEAASEwoPTUVUSE9ET1BUMV9WQUwxEAESEwoPTUVUSE9ET1BUMV9WQUwy",
+            "EAIqXgoNQWdncmVnYXRlRW51bRIPCgtVTlNQRUNJRklFRBAAEiUKBVZBTFVF",
+            "EAEaGsr8iTsVEhNFbnVtVmFsdWVBbm5vdGF0aW9uGhWSlYg7EBIORW51bUFu",
+            "bm90YXRpb24yjgEKHFRlc3RTZXJ2aWNlV2l0aEN1c3RvbU9wdGlvbnMSYwoD",
+            "Rm9vEikucHJvdG9idWZfdW5pdHRlc3QuQ3VzdG9tT3B0aW9uRm9vUmVxdWVz",
+            "dBoqLnByb3RvYnVmX3VuaXR0ZXN0LkN1c3RvbU9wdGlvbkZvb1Jlc3BvbnNl",
+            "IgXg+oweAhoJkLKLHtPbgMtJMpkBChBBZ2dyZWdhdGVTZXJ2aWNlEmsKBk1l",
+            "dGhvZBIjLnByb3RvYnVmX3VuaXR0ZXN0LkFnZ3JlZ2F0ZU1lc3NhZ2UaIy5w",
+            "cm90b2J1Zl91bml0dGVzdC5BZ2dyZWdhdGVNZXNzYWdlIhfKyJY7EhIQTWV0",
+            "aG9kQW5ub3RhdGlvbhoYyvuOOxMSEVNlcnZpY2VBbm5vdGF0aW9uOjIKCWZp",
+            "bGVfb3B0MRIcLmdvb2dsZS5wcm90b2J1Zi5GaWxlT3B0aW9ucxiOndgDIAEo",
+            "BDo4CgxtZXNzYWdlX29wdDESHy5nb29nbGUucHJvdG9idWYuTWVzc2FnZU9w",
+            "dGlvbnMYnK3YAyABKAU6NAoKZmllbGRfb3B0MRIdLmdvb2dsZS5wcm90b2J1",
+            "Zi5GaWVsZE9wdGlvbnMYiLzYAyABKAY6MgoJZW51bV9vcHQxEhwuZ29vZ2xl",
+            "LnByb3RvYnVmLkVudW1PcHRpb25zGOie2QMgASgPOjwKD2VudW1fdmFsdWVf",
+            "b3B0MRIhLmdvb2dsZS5wcm90b2J1Zi5FbnVtVmFsdWVPcHRpb25zGOagXyAB",
+            "KAU6OAoMc2VydmljZV9vcHQxEh8uZ29vZ2xlLnByb3RvYnVmLlNlcnZpY2VP",
+            "cHRpb25zGKK24QMgASgSOlUKC21ldGhvZF9vcHQxEh4uZ29vZ2xlLnByb3Rv",
+            "YnVmLk1ldGhvZE9wdGlvbnMYrM/hAyABKA4yHS5wcm90b2J1Zl91bml0dGVz",
+            "dC5NZXRob2RPcHQxOjQKCGJvb2xfb3B0Eh8uZ29vZ2xlLnByb3RvYnVmLk1l",
+            "c3NhZ2VPcHRpb25zGOqr1gMgASgIOjUKCWludDMyX29wdBIfLmdvb2dsZS5w",
+            "cm90b2J1Zi5NZXNzYWdlT3B0aW9ucxjtqNYDIAEoBTo1CglpbnQ2NF9vcHQS",
+            "Hy5nb29nbGUucHJvdG9idWYuTWVzc2FnZU9wdGlvbnMYxqfWAyABKAM6NgoK",
+            "dWludDMyX29wdBIfLmdvb2dsZS5wcm90b2J1Zi5NZXNzYWdlT3B0aW9ucxiw",
+            "otYDIAEoDTo2Cgp1aW50NjRfb3B0Eh8uZ29vZ2xlLnByb3RvYnVmLk1lc3Nh",
+            "Z2VPcHRpb25zGN+O1gMgASgEOjYKCnNpbnQzMl9vcHQSHy5nb29nbGUucHJv",
+            "dG9idWYuTWVzc2FnZU9wdGlvbnMYwIjWAyABKBE6NgoKc2ludDY0X29wdBIf",
+            "Lmdvb2dsZS5wcm90b2J1Zi5NZXNzYWdlT3B0aW9ucxj/gtYDIAEoEjo3Cgtm",
+            "aXhlZDMyX29wdBIfLmdvb2dsZS5wcm90b2J1Zi5NZXNzYWdlT3B0aW9ucxjT",
+            "/tUDIAEoBzo3CgtmaXhlZDY0X29wdBIfLmdvb2dsZS5wcm90b2J1Zi5NZXNz",
+            "YWdlT3B0aW9ucxji/dUDIAEoBjo4CgxzZml4ZWQzMl9vcHQSHy5nb29nbGUu",
+            "cHJvdG9idWYuTWVzc2FnZU9wdGlvbnMY1fHVAyABKA86OAoMc2ZpeGVkNjRf",
+            "b3B0Eh8uZ29vZ2xlLnByb3RvYnVmLk1lc3NhZ2VPcHRpb25zGOOK1QMgASgQ",
+            "OjUKCWZsb2F0X29wdBIfLmdvb2dsZS5wcm90b2J1Zi5NZXNzYWdlT3B0aW9u",
+            "cxj+u9QDIAEoAjo2Cgpkb3VibGVfb3B0Eh8uZ29vZ2xlLnByb3RvYnVmLk1l",
+            "c3NhZ2VPcHRpb25zGM2r1AMgASgBOjYKCnN0cmluZ19vcHQSHy5nb29nbGUu",
+            "cHJvdG9idWYuTWVzc2FnZU9wdGlvbnMYxavUAyABKAk6NQoJYnl0ZXNfb3B0",
+            "Eh8uZ29vZ2xlLnByb3RvYnVmLk1lc3NhZ2VPcHRpb25zGJar1AMgASgMOnAK",
+            "CGVudW1fb3B0Eh8uZ29vZ2xlLnByb3RvYnVmLk1lc3NhZ2VPcHRpb25zGJGr",
+            "1AMgASgOMjoucHJvdG9idWZfdW5pdHRlc3QuRHVtbXlNZXNzYWdlQ29udGFp",
+            "bmluZ0VudW0uVGVzdEVudW1UeXBlOnAKEG1lc3NhZ2VfdHlwZV9vcHQSHy5n",
+            "b29nbGUucHJvdG9idWYuTWVzc2FnZU9wdGlvbnMYr/LTAyABKAsyMi5wcm90",
+            "b2J1Zl91bml0dGVzdC5EdW1teU1lc3NhZ2VJbnZhbGlkQXNPcHRpb25UeXBl",
+            "Ol8KDGNvbXBsZXhfb3B0MRIfLmdvb2dsZS5wcm90b2J1Zi5NZXNzYWdlT3B0",
+            "aW9ucxik3NIDIAEoCzIlLnByb3RvYnVmX3VuaXR0ZXN0LkNvbXBsZXhPcHRp",
+            "b25UeXBlMTpfCgxjb21wbGV4X29wdDISHy5nb29nbGUucHJvdG9idWYuTWVz",
+            "c2FnZU9wdGlvbnMY1Y/SAyABKAsyJS5wcm90b2J1Zl91bml0dGVzdC5Db21w",
+            "bGV4T3B0aW9uVHlwZTI6XwoMY29tcGxleF9vcHQzEh8uZ29vZ2xlLnByb3Rv",
+            "YnVmLk1lc3NhZ2VPcHRpb25zGO+L0gMgASgLMiUucHJvdG9idWZfdW5pdHRl",
+            "c3QuQ29tcGxleE9wdGlvblR5cGUzOk4KB2ZpbGVvcHQSHC5nb29nbGUucHJv",
+            "dG9idWYuRmlsZU9wdGlvbnMYz92wByABKAsyHC5wcm90b2J1Zl91bml0dGVz",
+            "dC5BZ2dyZWdhdGU6UAoGbXNnb3B0Eh8uZ29vZ2xlLnByb3RvYnVmLk1lc3Nh",
+            "Z2VPcHRpb25zGJjqsAcgASgLMhwucHJvdG9idWZfdW5pdHRlc3QuQWdncmVn",
+            "YXRlOlAKCGZpZWxkb3B0Eh0uZ29vZ2xlLnByb3RvYnVmLkZpZWxkT3B0aW9u",
+            "cxie9LAHIAEoCzIcLnByb3RvYnVmX3VuaXR0ZXN0LkFnZ3JlZ2F0ZTpOCgdl",
+            "bnVtb3B0EhwuZ29vZ2xlLnByb3RvYnVmLkVudW1PcHRpb25zGNKCsQcgASgL",
+            "MhwucHJvdG9idWZfdW5pdHRlc3QuQWdncmVnYXRlOlYKCmVudW12YWxvcHQS",
+            "IS5nb29nbGUucHJvdG9idWYuRW51bVZhbHVlT3B0aW9ucxjJn7EHIAEoCzIc",
+            "LnByb3RvYnVmX3VuaXR0ZXN0LkFnZ3JlZ2F0ZTpUCgpzZXJ2aWNlb3B0Eh8u",
+            "Z29vZ2xlLnByb3RvYnVmLlNlcnZpY2VPcHRpb25zGLnvsQcgASgLMhwucHJv",
+            "dG9idWZfdW5pdHRlc3QuQWdncmVnYXRlOlIKCW1ldGhvZG9wdBIeLmdvb2ds",
+            "ZS5wcm90b2J1Zi5NZXRob2RPcHRpb25zGInpsgcgASgLMhwucHJvdG9idWZf",
+            "dW5pdHRlc3QuQWdncmVnYXRlQlWqAhpVbml0VGVzdC5Jc3N1ZXMuVGVzdFBy",
+            "b3Rvc/DowR3qrcDlJPrshTsqCGQSDkZpbGVBbm5vdGF0aW9uGhYSFE5lc3Rl",
+            "ZEZpbGVBbm5vdGF0aW9uYgZwcm90bzM="));
+      descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData,
+          new pbr::FileDescriptor[] { pbr::FileDescriptor.DescriptorProtoFileDescriptor, },
+          new pbr::GeneratedClrTypeInfo(new[] {typeof(global::UnitTest.Issues.TestProtos.MethodOpt1), typeof(global::UnitTest.Issues.TestProtos.AggregateEnum), }, new pbr::GeneratedClrTypeInfo[] {
+            new pbr::GeneratedClrTypeInfo(typeof(global::UnitTest.Issues.TestProtos.TestMessageWithCustomOptions), global::UnitTest.Issues.TestProtos.TestMessageWithCustomOptions.Parser, new[]{ "Field1", "OneofField" }, new[]{ "AnOneof" }, new[]{ typeof(global::UnitTest.Issues.TestProtos.TestMessageWithCustomOptions.Types.AnEnum) }, null),
+            new pbr::GeneratedClrTypeInfo(typeof(global::UnitTest.Issues.TestProtos.CustomOptionFooRequest), global::UnitTest.Issues.TestProtos.CustomOptionFooRequest.Parser, null, null, null, null),
+            new pbr::GeneratedClrTypeInfo(typeof(global::UnitTest.Issues.TestProtos.CustomOptionFooResponse), global::UnitTest.Issues.TestProtos.CustomOptionFooResponse.Parser, null, null, null, null),
+            new pbr::GeneratedClrTypeInfo(typeof(global::UnitTest.Issues.TestProtos.CustomOptionFooClientMessage), global::UnitTest.Issues.TestProtos.CustomOptionFooClientMessage.Parser, null, null, null, null),
+            new pbr::GeneratedClrTypeInfo(typeof(global::UnitTest.Issues.TestProtos.CustomOptionFooServerMessage), global::UnitTest.Issues.TestProtos.CustomOptionFooServerMessage.Parser, null, null, null, null),
+            new pbr::GeneratedClrTypeInfo(typeof(global::UnitTest.Issues.TestProtos.DummyMessageContainingEnum), global::UnitTest.Issues.TestProtos.DummyMessageContainingEnum.Parser, null, null, new[]{ typeof(global::UnitTest.Issues.TestProtos.DummyMessageContainingEnum.Types.TestEnumType) }, null),
+            new pbr::GeneratedClrTypeInfo(typeof(global::UnitTest.Issues.TestProtos.DummyMessageInvalidAsOptionType), global::UnitTest.Issues.TestProtos.DummyMessageInvalidAsOptionType.Parser, null, null, null, null),
+            new pbr::GeneratedClrTypeInfo(typeof(global::UnitTest.Issues.TestProtos.CustomOptionMinIntegerValues), global::UnitTest.Issues.TestProtos.CustomOptionMinIntegerValues.Parser, null, null, null, null),
+            new pbr::GeneratedClrTypeInfo(typeof(global::UnitTest.Issues.TestProtos.CustomOptionMaxIntegerValues), global::UnitTest.Issues.TestProtos.CustomOptionMaxIntegerValues.Parser, null, null, null, null),
+            new pbr::GeneratedClrTypeInfo(typeof(global::UnitTest.Issues.TestProtos.CustomOptionOtherValues), global::UnitTest.Issues.TestProtos.CustomOptionOtherValues.Parser, null, null, null, null),
+            new pbr::GeneratedClrTypeInfo(typeof(global::UnitTest.Issues.TestProtos.SettingRealsFromPositiveInts), global::UnitTest.Issues.TestProtos.SettingRealsFromPositiveInts.Parser, null, null, null, null),
+            new pbr::GeneratedClrTypeInfo(typeof(global::UnitTest.Issues.TestProtos.SettingRealsFromNegativeInts), global::UnitTest.Issues.TestProtos.SettingRealsFromNegativeInts.Parser, null, null, null, null),
+            new pbr::GeneratedClrTypeInfo(typeof(global::UnitTest.Issues.TestProtos.ComplexOptionType1), global::UnitTest.Issues.TestProtos.ComplexOptionType1.Parser, new[]{ "Foo", "Foo2", "Foo3", "Foo4" }, null, null, null),
+            new pbr::GeneratedClrTypeInfo(typeof(global::UnitTest.Issues.TestProtos.ComplexOptionType2), global::UnitTest.Issues.TestProtos.ComplexOptionType2.Parser, new[]{ "Bar", "Baz", "Fred", "Barney" }, null, null, new pbr::GeneratedClrTypeInfo[] { new pbr::GeneratedClrTypeInfo(typeof(global::UnitTest.Issues.TestProtos.ComplexOptionType2.Types.ComplexOptionType4), global::UnitTest.Issues.TestProtos.ComplexOptionType2.Types.ComplexOptionType4.Parser, new[]{ "Waldo" }, null, null, null)}),
+            new pbr::GeneratedClrTypeInfo(typeof(global::UnitTest.Issues.TestProtos.ComplexOptionType3), global::UnitTest.Issues.TestProtos.ComplexOptionType3.Parser, new[]{ "Qux" }, null, null, null),
+            new pbr::GeneratedClrTypeInfo(typeof(global::UnitTest.Issues.TestProtos.VariousComplexOptions), global::UnitTest.Issues.TestProtos.VariousComplexOptions.Parser, null, null, null, null),
+            new pbr::GeneratedClrTypeInfo(typeof(global::UnitTest.Issues.TestProtos.Aggregate), global::UnitTest.Issues.TestProtos.Aggregate.Parser, new[]{ "I", "S", "Sub" }, null, null, null),
+            new pbr::GeneratedClrTypeInfo(typeof(global::UnitTest.Issues.TestProtos.AggregateMessage), global::UnitTest.Issues.TestProtos.AggregateMessage.Parser, new[]{ "Fieldname" }, null, null, null),
+            new pbr::GeneratedClrTypeInfo(typeof(global::UnitTest.Issues.TestProtos.NestedOptionType), global::UnitTest.Issues.TestProtos.NestedOptionType.Parser, null, null, new[]{ typeof(global::UnitTest.Issues.TestProtos.NestedOptionType.Types.NestedEnum) }, new pbr::GeneratedClrTypeInfo[] { new pbr::GeneratedClrTypeInfo(typeof(global::UnitTest.Issues.TestProtos.NestedOptionType.Types.NestedMessage), global::UnitTest.Issues.TestProtos.NestedOptionType.Types.NestedMessage.Parser, new[]{ "NestedField" }, null, null, null)})
+          }));
+    }
+    #endregion
+
+  }
+  #region Enums
+  public enum MethodOpt1 {
+    [pbr::OriginalName("METHODOPT1_UNSPECIFIED")] Unspecified = 0,
+    [pbr::OriginalName("METHODOPT1_VAL1")] Val1 = 1,
+    [pbr::OriginalName("METHODOPT1_VAL2")] Val2 = 2,
+  }
+
+  public enum AggregateEnum {
+    [pbr::OriginalName("UNSPECIFIED")] Unspecified = 0,
+    [pbr::OriginalName("VALUE")] Value = 1,
+  }
+
+  #endregion
+
+  #region Messages
+  /// <summary>
+  /// A test message with custom options at all possible locations (and also some
+  /// regular options, to make sure they interact nicely).
+  /// </summary>
+  public sealed partial class TestMessageWithCustomOptions : pb::IMessage<TestMessageWithCustomOptions> {
+    private static readonly pb::MessageParser<TestMessageWithCustomOptions> _parser = new pb::MessageParser<TestMessageWithCustomOptions>(() => new TestMessageWithCustomOptions());
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public static pb::MessageParser<TestMessageWithCustomOptions> Parser { get { return _parser; } }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public static pbr::MessageDescriptor Descriptor {
+      get { return global::UnitTest.Issues.TestProtos.UnittestCustomOptionsProto3Reflection.Descriptor.MessageTypes[0]; }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    pbr::MessageDescriptor pb::IMessage.Descriptor {
+      get { return Descriptor; }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public TestMessageWithCustomOptions() {
+      OnConstruction();
+    }
+
+    partial void OnConstruction();
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public TestMessageWithCustomOptions(TestMessageWithCustomOptions other) : this() {
+      field1_ = other.field1_;
+      switch (other.AnOneofCase) {
+        case AnOneofOneofCase.OneofField:
+          OneofField = other.OneofField;
+          break;
+      }
+
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public TestMessageWithCustomOptions Clone() {
+      return new TestMessageWithCustomOptions(this);
+    }
+
+    /// <summary>Field number for the "field1" field.</summary>
+    public const int Field1FieldNumber = 1;
+    private string field1_ = "";
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public string Field1 {
+      get { return field1_; }
+      set {
+        field1_ = pb::ProtoPreconditions.CheckNotNull(value, "value");
+      }
+    }
+
+    /// <summary>Field number for the "oneof_field" field.</summary>
+    public const int OneofFieldFieldNumber = 2;
+    /// <summary>
+    /// See https://github.com/google/protobuf/issues/2316
+    ///    option (oneof_opt1) = -99;
+    /// </summary>
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public int OneofField {
+      get { return anOneofCase_ == AnOneofOneofCase.OneofField ? (int) anOneof_ : 0; }
+      set {
+        anOneof_ = value;
+        anOneofCase_ = AnOneofOneofCase.OneofField;
+      }
+    }
+
+    private object anOneof_;
+    /// <summary>Enum of possible cases for the "AnOneof" oneof.</summary>
+    public enum AnOneofOneofCase {
+      None = 0,
+      OneofField = 2,
+    }
+    private AnOneofOneofCase anOneofCase_ = AnOneofOneofCase.None;
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public AnOneofOneofCase AnOneofCase {
+      get { return anOneofCase_; }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public void ClearAnOneof() {
+      anOneofCase_ = AnOneofOneofCase.None;
+      anOneof_ = null;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public override bool Equals(object other) {
+      return Equals(other as TestMessageWithCustomOptions);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public bool Equals(TestMessageWithCustomOptions other) {
+      if (ReferenceEquals(other, null)) {
+        return false;
+      }
+      if (ReferenceEquals(other, this)) {
+        return true;
+      }
+      if (Field1 != other.Field1) return false;
+      if (OneofField != other.OneofField) return false;
+      if (AnOneofCase != other.AnOneofCase) return false;
+      return true;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public override int GetHashCode() {
+      int hash = 1;
+      if (Field1.Length != 0) hash ^= Field1.GetHashCode();
+      if (anOneofCase_ == AnOneofOneofCase.OneofField) hash ^= OneofField.GetHashCode();
+      hash ^= (int) anOneofCase_;
+      return hash;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public override string ToString() {
+      return pb::JsonFormatter.ToDiagnosticString(this);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public void WriteTo(pb::CodedOutputStream output) {
+      if (Field1.Length != 0) {
+        output.WriteRawTag(10);
+        output.WriteString(Field1);
+      }
+      if (anOneofCase_ == AnOneofOneofCase.OneofField) {
+        output.WriteRawTag(16);
+        output.WriteInt32(OneofField);
+      }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public int CalculateSize() {
+      int size = 0;
+      if (Field1.Length != 0) {
+        size += 1 + pb::CodedOutputStream.ComputeStringSize(Field1);
+      }
+      if (anOneofCase_ == AnOneofOneofCase.OneofField) {
+        size += 1 + pb::CodedOutputStream.ComputeInt32Size(OneofField);
+      }
+      return size;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public void MergeFrom(TestMessageWithCustomOptions other) {
+      if (other == null) {
+        return;
+      }
+      if (other.Field1.Length != 0) {
+        Field1 = other.Field1;
+      }
+      switch (other.AnOneofCase) {
+        case AnOneofOneofCase.OneofField:
+          OneofField = other.OneofField;
+          break;
+      }
+
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public void MergeFrom(pb::CodedInputStream input) {
+      uint tag;
+      while ((tag = input.ReadTag()) != 0) {
+        switch(tag) {
+          default:
+            input.SkipLastField();
+            break;
+          case 10: {
+            Field1 = input.ReadString();
+            break;
+          }
+          case 16: {
+            OneofField = input.ReadInt32();
+            break;
+          }
+        }
+      }
+    }
+
+    #region Nested types
+    /// <summary>Container for nested types declared in the TestMessageWithCustomOptions message type.</summary>
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public static partial class Types {
+      public enum AnEnum {
+        [pbr::OriginalName("ANENUM_UNSPECIFIED")] Unspecified = 0,
+        [pbr::OriginalName("ANENUM_VAL1")] Val1 = 1,
+        [pbr::OriginalName("ANENUM_VAL2")] Val2 = 2,
+      }
+
+    }
+    #endregion
+
+  }
+
+  /// <summary>
+  /// A test RPC service with custom options at all possible locations (and also
+  /// some regular options, to make sure they interact nicely).
+  /// </summary>
+  public sealed partial class CustomOptionFooRequest : pb::IMessage<CustomOptionFooRequest> {
+    private static readonly pb::MessageParser<CustomOptionFooRequest> _parser = new pb::MessageParser<CustomOptionFooRequest>(() => new CustomOptionFooRequest());
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public static pb::MessageParser<CustomOptionFooRequest> Parser { get { return _parser; } }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public static pbr::MessageDescriptor Descriptor {
+      get { return global::UnitTest.Issues.TestProtos.UnittestCustomOptionsProto3Reflection.Descriptor.MessageTypes[1]; }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    pbr::MessageDescriptor pb::IMessage.Descriptor {
+      get { return Descriptor; }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public CustomOptionFooRequest() {
+      OnConstruction();
+    }
+
+    partial void OnConstruction();
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public CustomOptionFooRequest(CustomOptionFooRequest other) : this() {
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public CustomOptionFooRequest Clone() {
+      return new CustomOptionFooRequest(this);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public override bool Equals(object other) {
+      return Equals(other as CustomOptionFooRequest);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public bool Equals(CustomOptionFooRequest other) {
+      if (ReferenceEquals(other, null)) {
+        return false;
+      }
+      if (ReferenceEquals(other, this)) {
+        return true;
+      }
+      return true;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public override int GetHashCode() {
+      int hash = 1;
+      return hash;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public override string ToString() {
+      return pb::JsonFormatter.ToDiagnosticString(this);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public void WriteTo(pb::CodedOutputStream output) {
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public int CalculateSize() {
+      int size = 0;
+      return size;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public void MergeFrom(CustomOptionFooRequest other) {
+      if (other == null) {
+        return;
+      }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public void MergeFrom(pb::CodedInputStream input) {
+      uint tag;
+      while ((tag = input.ReadTag()) != 0) {
+        switch(tag) {
+          default:
+            input.SkipLastField();
+            break;
+        }
+      }
+    }
+
+  }
+
+  public sealed partial class CustomOptionFooResponse : pb::IMessage<CustomOptionFooResponse> {
+    private static readonly pb::MessageParser<CustomOptionFooResponse> _parser = new pb::MessageParser<CustomOptionFooResponse>(() => new CustomOptionFooResponse());
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public static pb::MessageParser<CustomOptionFooResponse> Parser { get { return _parser; } }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public static pbr::MessageDescriptor Descriptor {
+      get { return global::UnitTest.Issues.TestProtos.UnittestCustomOptionsProto3Reflection.Descriptor.MessageTypes[2]; }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    pbr::MessageDescriptor pb::IMessage.Descriptor {
+      get { return Descriptor; }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public CustomOptionFooResponse() {
+      OnConstruction();
+    }
+
+    partial void OnConstruction();
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public CustomOptionFooResponse(CustomOptionFooResponse other) : this() {
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public CustomOptionFooResponse Clone() {
+      return new CustomOptionFooResponse(this);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public override bool Equals(object other) {
+      return Equals(other as CustomOptionFooResponse);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public bool Equals(CustomOptionFooResponse other) {
+      if (ReferenceEquals(other, null)) {
+        return false;
+      }
+      if (ReferenceEquals(other, this)) {
+        return true;
+      }
+      return true;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public override int GetHashCode() {
+      int hash = 1;
+      return hash;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public override string ToString() {
+      return pb::JsonFormatter.ToDiagnosticString(this);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public void WriteTo(pb::CodedOutputStream output) {
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public int CalculateSize() {
+      int size = 0;
+      return size;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public void MergeFrom(CustomOptionFooResponse other) {
+      if (other == null) {
+        return;
+      }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public void MergeFrom(pb::CodedInputStream input) {
+      uint tag;
+      while ((tag = input.ReadTag()) != 0) {
+        switch(tag) {
+          default:
+            input.SkipLastField();
+            break;
+        }
+      }
+    }
+
+  }
+
+  public sealed partial class CustomOptionFooClientMessage : pb::IMessage<CustomOptionFooClientMessage> {
+    private static readonly pb::MessageParser<CustomOptionFooClientMessage> _parser = new pb::MessageParser<CustomOptionFooClientMessage>(() => new CustomOptionFooClientMessage());
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public static pb::MessageParser<CustomOptionFooClientMessage> Parser { get { return _parser; } }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public static pbr::MessageDescriptor Descriptor {
+      get { return global::UnitTest.Issues.TestProtos.UnittestCustomOptionsProto3Reflection.Descriptor.MessageTypes[3]; }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    pbr::MessageDescriptor pb::IMessage.Descriptor {
+      get { return Descriptor; }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public CustomOptionFooClientMessage() {
+      OnConstruction();
+    }
+
+    partial void OnConstruction();
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public CustomOptionFooClientMessage(CustomOptionFooClientMessage other) : this() {
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public CustomOptionFooClientMessage Clone() {
+      return new CustomOptionFooClientMessage(this);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public override bool Equals(object other) {
+      return Equals(other as CustomOptionFooClientMessage);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public bool Equals(CustomOptionFooClientMessage other) {
+      if (ReferenceEquals(other, null)) {
+        return false;
+      }
+      if (ReferenceEquals(other, this)) {
+        return true;
+      }
+      return true;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public override int GetHashCode() {
+      int hash = 1;
+      return hash;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public override string ToString() {
+      return pb::JsonFormatter.ToDiagnosticString(this);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public void WriteTo(pb::CodedOutputStream output) {
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public int CalculateSize() {
+      int size = 0;
+      return size;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public void MergeFrom(CustomOptionFooClientMessage other) {
+      if (other == null) {
+        return;
+      }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public void MergeFrom(pb::CodedInputStream input) {
+      uint tag;
+      while ((tag = input.ReadTag()) != 0) {
+        switch(tag) {
+          default:
+            input.SkipLastField();
+            break;
+        }
+      }
+    }
+
+  }
+
+  public sealed partial class CustomOptionFooServerMessage : pb::IMessage<CustomOptionFooServerMessage> {
+    private static readonly pb::MessageParser<CustomOptionFooServerMessage> _parser = new pb::MessageParser<CustomOptionFooServerMessage>(() => new CustomOptionFooServerMessage());
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public static pb::MessageParser<CustomOptionFooServerMessage> Parser { get { return _parser; } }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public static pbr::MessageDescriptor Descriptor {
+      get { return global::UnitTest.Issues.TestProtos.UnittestCustomOptionsProto3Reflection.Descriptor.MessageTypes[4]; }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    pbr::MessageDescriptor pb::IMessage.Descriptor {
+      get { return Descriptor; }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public CustomOptionFooServerMessage() {
+      OnConstruction();
+    }
+
+    partial void OnConstruction();
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public CustomOptionFooServerMessage(CustomOptionFooServerMessage other) : this() {
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public CustomOptionFooServerMessage Clone() {
+      return new CustomOptionFooServerMessage(this);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public override bool Equals(object other) {
+      return Equals(other as CustomOptionFooServerMessage);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public bool Equals(CustomOptionFooServerMessage other) {
+      if (ReferenceEquals(other, null)) {
+        return false;
+      }
+      if (ReferenceEquals(other, this)) {
+        return true;
+      }
+      return true;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public override int GetHashCode() {
+      int hash = 1;
+      return hash;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public override string ToString() {
+      return pb::JsonFormatter.ToDiagnosticString(this);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public void WriteTo(pb::CodedOutputStream output) {
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public int CalculateSize() {
+      int size = 0;
+      return size;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public void MergeFrom(CustomOptionFooServerMessage other) {
+      if (other == null) {
+        return;
+      }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public void MergeFrom(pb::CodedInputStream input) {
+      uint tag;
+      while ((tag = input.ReadTag()) != 0) {
+        switch(tag) {
+          default:
+            input.SkipLastField();
+            break;
+        }
+      }
+    }
+
+  }
+
+  public sealed partial class DummyMessageContainingEnum : pb::IMessage<DummyMessageContainingEnum> {
+    private static readonly pb::MessageParser<DummyMessageContainingEnum> _parser = new pb::MessageParser<DummyMessageContainingEnum>(() => new DummyMessageContainingEnum());
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public static pb::MessageParser<DummyMessageContainingEnum> Parser { get { return _parser; } }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public static pbr::MessageDescriptor Descriptor {
+      get { return global::UnitTest.Issues.TestProtos.UnittestCustomOptionsProto3Reflection.Descriptor.MessageTypes[5]; }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    pbr::MessageDescriptor pb::IMessage.Descriptor {
+      get { return Descriptor; }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public DummyMessageContainingEnum() {
+      OnConstruction();
+    }
+
+    partial void OnConstruction();
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public DummyMessageContainingEnum(DummyMessageContainingEnum other) : this() {
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public DummyMessageContainingEnum Clone() {
+      return new DummyMessageContainingEnum(this);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public override bool Equals(object other) {
+      return Equals(other as DummyMessageContainingEnum);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public bool Equals(DummyMessageContainingEnum other) {
+      if (ReferenceEquals(other, null)) {
+        return false;
+      }
+      if (ReferenceEquals(other, this)) {
+        return true;
+      }
+      return true;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public override int GetHashCode() {
+      int hash = 1;
+      return hash;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public override string ToString() {
+      return pb::JsonFormatter.ToDiagnosticString(this);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public void WriteTo(pb::CodedOutputStream output) {
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public int CalculateSize() {
+      int size = 0;
+      return size;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public void MergeFrom(DummyMessageContainingEnum other) {
+      if (other == null) {
+        return;
+      }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public void MergeFrom(pb::CodedInputStream input) {
+      uint tag;
+      while ((tag = input.ReadTag()) != 0) {
+        switch(tag) {
+          default:
+            input.SkipLastField();
+            break;
+        }
+      }
+    }
+
+    #region Nested types
+    /// <summary>Container for nested types declared in the DummyMessageContainingEnum message type.</summary>
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public static partial class Types {
+      public enum TestEnumType {
+        [pbr::OriginalName("TEST_OPTION_ENUM_UNSPECIFIED")] TestOptionEnumUnspecified = 0,
+        [pbr::OriginalName("TEST_OPTION_ENUM_TYPE1")] TestOptionEnumType1 = 22,
+        [pbr::OriginalName("TEST_OPTION_ENUM_TYPE2")] TestOptionEnumType2 = -23,
+      }
+
+    }
+    #endregion
+
+  }
+
+  public sealed partial class DummyMessageInvalidAsOptionType : pb::IMessage<DummyMessageInvalidAsOptionType> {
+    private static readonly pb::MessageParser<DummyMessageInvalidAsOptionType> _parser = new pb::MessageParser<DummyMessageInvalidAsOptionType>(() => new DummyMessageInvalidAsOptionType());
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public static pb::MessageParser<DummyMessageInvalidAsOptionType> Parser { get { return _parser; } }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public static pbr::MessageDescriptor Descriptor {
+      get { return global::UnitTest.Issues.TestProtos.UnittestCustomOptionsProto3Reflection.Descriptor.MessageTypes[6]; }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    pbr::MessageDescriptor pb::IMessage.Descriptor {
+      get { return Descriptor; }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public DummyMessageInvalidAsOptionType() {
+      OnConstruction();
+    }
+
+    partial void OnConstruction();
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public DummyMessageInvalidAsOptionType(DummyMessageInvalidAsOptionType other) : this() {
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public DummyMessageInvalidAsOptionType Clone() {
+      return new DummyMessageInvalidAsOptionType(this);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public override bool Equals(object other) {
+      return Equals(other as DummyMessageInvalidAsOptionType);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public bool Equals(DummyMessageInvalidAsOptionType other) {
+      if (ReferenceEquals(other, null)) {
+        return false;
+      }
+      if (ReferenceEquals(other, this)) {
+        return true;
+      }
+      return true;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public override int GetHashCode() {
+      int hash = 1;
+      return hash;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public override string ToString() {
+      return pb::JsonFormatter.ToDiagnosticString(this);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public void WriteTo(pb::CodedOutputStream output) {
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public int CalculateSize() {
+      int size = 0;
+      return size;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public void MergeFrom(DummyMessageInvalidAsOptionType other) {
+      if (other == null) {
+        return;
+      }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public void MergeFrom(pb::CodedInputStream input) {
+      uint tag;
+      while ((tag = input.ReadTag()) != 0) {
+        switch(tag) {
+          default:
+            input.SkipLastField();
+            break;
+        }
+      }
+    }
+
+  }
+
+  public sealed partial class CustomOptionMinIntegerValues : pb::IMessage<CustomOptionMinIntegerValues> {
+    private static readonly pb::MessageParser<CustomOptionMinIntegerValues> _parser = new pb::MessageParser<CustomOptionMinIntegerValues>(() => new CustomOptionMinIntegerValues());
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public static pb::MessageParser<CustomOptionMinIntegerValues> Parser { get { return _parser; } }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public static pbr::MessageDescriptor Descriptor {
+      get { return global::UnitTest.Issues.TestProtos.UnittestCustomOptionsProto3Reflection.Descriptor.MessageTypes[7]; }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    pbr::MessageDescriptor pb::IMessage.Descriptor {
+      get { return Descriptor; }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public CustomOptionMinIntegerValues() {
+      OnConstruction();
+    }
+
+    partial void OnConstruction();
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public CustomOptionMinIntegerValues(CustomOptionMinIntegerValues other) : this() {
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public CustomOptionMinIntegerValues Clone() {
+      return new CustomOptionMinIntegerValues(this);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public override bool Equals(object other) {
+      return Equals(other as CustomOptionMinIntegerValues);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public bool Equals(CustomOptionMinIntegerValues other) {
+      if (ReferenceEquals(other, null)) {
+        return false;
+      }
+      if (ReferenceEquals(other, this)) {
+        return true;
+      }
+      return true;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public override int GetHashCode() {
+      int hash = 1;
+      return hash;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public override string ToString() {
+      return pb::JsonFormatter.ToDiagnosticString(this);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public void WriteTo(pb::CodedOutputStream output) {
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public int CalculateSize() {
+      int size = 0;
+      return size;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public void MergeFrom(CustomOptionMinIntegerValues other) {
+      if (other == null) {
+        return;
+      }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public void MergeFrom(pb::CodedInputStream input) {
+      uint tag;
+      while ((tag = input.ReadTag()) != 0) {
+        switch(tag) {
+          default:
+            input.SkipLastField();
+            break;
+        }
+      }
+    }
+
+  }
+
+  public sealed partial class CustomOptionMaxIntegerValues : pb::IMessage<CustomOptionMaxIntegerValues> {
+    private static readonly pb::MessageParser<CustomOptionMaxIntegerValues> _parser = new pb::MessageParser<CustomOptionMaxIntegerValues>(() => new CustomOptionMaxIntegerValues());
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public static pb::MessageParser<CustomOptionMaxIntegerValues> Parser { get { return _parser; } }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public static pbr::MessageDescriptor Descriptor {
+      get { return global::UnitTest.Issues.TestProtos.UnittestCustomOptionsProto3Reflection.Descriptor.MessageTypes[8]; }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    pbr::MessageDescriptor pb::IMessage.Descriptor {
+      get { return Descriptor; }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public CustomOptionMaxIntegerValues() {
+      OnConstruction();
+    }
+
+    partial void OnConstruction();
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public CustomOptionMaxIntegerValues(CustomOptionMaxIntegerValues other) : this() {
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public CustomOptionMaxIntegerValues Clone() {
+      return new CustomOptionMaxIntegerValues(this);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public override bool Equals(object other) {
+      return Equals(other as CustomOptionMaxIntegerValues);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public bool Equals(CustomOptionMaxIntegerValues other) {
+      if (ReferenceEquals(other, null)) {
+        return false;
+      }
+      if (ReferenceEquals(other, this)) {
+        return true;
+      }
+      return true;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public override int GetHashCode() {
+      int hash = 1;
+      return hash;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public override string ToString() {
+      return pb::JsonFormatter.ToDiagnosticString(this);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public void WriteTo(pb::CodedOutputStream output) {
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public int CalculateSize() {
+      int size = 0;
+      return size;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public void MergeFrom(CustomOptionMaxIntegerValues other) {
+      if (other == null) {
+        return;
+      }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public void MergeFrom(pb::CodedInputStream input) {
+      uint tag;
+      while ((tag = input.ReadTag()) != 0) {
+        switch(tag) {
+          default:
+            input.SkipLastField();
+            break;
+        }
+      }
+    }
+
+  }
+
+  public sealed partial class CustomOptionOtherValues : pb::IMessage<CustomOptionOtherValues> {
+    private static readonly pb::MessageParser<CustomOptionOtherValues> _parser = new pb::MessageParser<CustomOptionOtherValues>(() => new CustomOptionOtherValues());
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public static pb::MessageParser<CustomOptionOtherValues> Parser { get { return _parser; } }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public static pbr::MessageDescriptor Descriptor {
+      get { return global::UnitTest.Issues.TestProtos.UnittestCustomOptionsProto3Reflection.Descriptor.MessageTypes[9]; }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    pbr::MessageDescriptor pb::IMessage.Descriptor {
+      get { return Descriptor; }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public CustomOptionOtherValues() {
+      OnConstruction();
+    }
+
+    partial void OnConstruction();
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public CustomOptionOtherValues(CustomOptionOtherValues other) : this() {
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public CustomOptionOtherValues Clone() {
+      return new CustomOptionOtherValues(this);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public override bool Equals(object other) {
+      return Equals(other as CustomOptionOtherValues);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public bool Equals(CustomOptionOtherValues other) {
+      if (ReferenceEquals(other, null)) {
+        return false;
+      }
+      if (ReferenceEquals(other, this)) {
+        return true;
+      }
+      return true;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public override int GetHashCode() {
+      int hash = 1;
+      return hash;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public override string ToString() {
+      return pb::JsonFormatter.ToDiagnosticString(this);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public void WriteTo(pb::CodedOutputStream output) {
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public int CalculateSize() {
+      int size = 0;
+      return size;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public void MergeFrom(CustomOptionOtherValues other) {
+      if (other == null) {
+        return;
+      }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public void MergeFrom(pb::CodedInputStream input) {
+      uint tag;
+      while ((tag = input.ReadTag()) != 0) {
+        switch(tag) {
+          default:
+            input.SkipLastField();
+            break;
+        }
+      }
+    }
+
+  }
+
+  public sealed partial class SettingRealsFromPositiveInts : pb::IMessage<SettingRealsFromPositiveInts> {
+    private static readonly pb::MessageParser<SettingRealsFromPositiveInts> _parser = new pb::MessageParser<SettingRealsFromPositiveInts>(() => new SettingRealsFromPositiveInts());
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public static pb::MessageParser<SettingRealsFromPositiveInts> Parser { get { return _parser; } }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public static pbr::MessageDescriptor Descriptor {
+      get { return global::UnitTest.Issues.TestProtos.UnittestCustomOptionsProto3Reflection.Descriptor.MessageTypes[10]; }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    pbr::MessageDescriptor pb::IMessage.Descriptor {
+      get { return Descriptor; }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public SettingRealsFromPositiveInts() {
+      OnConstruction();
+    }
+
+    partial void OnConstruction();
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public SettingRealsFromPositiveInts(SettingRealsFromPositiveInts other) : this() {
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public SettingRealsFromPositiveInts Clone() {
+      return new SettingRealsFromPositiveInts(this);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public override bool Equals(object other) {
+      return Equals(other as SettingRealsFromPositiveInts);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public bool Equals(SettingRealsFromPositiveInts other) {
+      if (ReferenceEquals(other, null)) {
+        return false;
+      }
+      if (ReferenceEquals(other, this)) {
+        return true;
+      }
+      return true;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public override int GetHashCode() {
+      int hash = 1;
+      return hash;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public override string ToString() {
+      return pb::JsonFormatter.ToDiagnosticString(this);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public void WriteTo(pb::CodedOutputStream output) {
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public int CalculateSize() {
+      int size = 0;
+      return size;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public void MergeFrom(SettingRealsFromPositiveInts other) {
+      if (other == null) {
+        return;
+      }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public void MergeFrom(pb::CodedInputStream input) {
+      uint tag;
+      while ((tag = input.ReadTag()) != 0) {
+        switch(tag) {
+          default:
+            input.SkipLastField();
+            break;
+        }
+      }
+    }
+
+  }
+
+  public sealed partial class SettingRealsFromNegativeInts : pb::IMessage<SettingRealsFromNegativeInts> {
+    private static readonly pb::MessageParser<SettingRealsFromNegativeInts> _parser = new pb::MessageParser<SettingRealsFromNegativeInts>(() => new SettingRealsFromNegativeInts());
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public static pb::MessageParser<SettingRealsFromNegativeInts> Parser { get { return _parser; } }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public static pbr::MessageDescriptor Descriptor {
+      get { return global::UnitTest.Issues.TestProtos.UnittestCustomOptionsProto3Reflection.Descriptor.MessageTypes[11]; }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    pbr::MessageDescriptor pb::IMessage.Descriptor {
+      get { return Descriptor; }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public SettingRealsFromNegativeInts() {
+      OnConstruction();
+    }
+
+    partial void OnConstruction();
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public SettingRealsFromNegativeInts(SettingRealsFromNegativeInts other) : this() {
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public SettingRealsFromNegativeInts Clone() {
+      return new SettingRealsFromNegativeInts(this);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public override bool Equals(object other) {
+      return Equals(other as SettingRealsFromNegativeInts);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public bool Equals(SettingRealsFromNegativeInts other) {
+      if (ReferenceEquals(other, null)) {
+        return false;
+      }
+      if (ReferenceEquals(other, this)) {
+        return true;
+      }
+      return true;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public override int GetHashCode() {
+      int hash = 1;
+      return hash;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public override string ToString() {
+      return pb::JsonFormatter.ToDiagnosticString(this);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public void WriteTo(pb::CodedOutputStream output) {
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public int CalculateSize() {
+      int size = 0;
+      return size;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public void MergeFrom(SettingRealsFromNegativeInts other) {
+      if (other == null) {
+        return;
+      }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public void MergeFrom(pb::CodedInputStream input) {
+      uint tag;
+      while ((tag = input.ReadTag()) != 0) {
+        switch(tag) {
+          default:
+            input.SkipLastField();
+            break;
+        }
+      }
+    }
+
+  }
+
+  public sealed partial class ComplexOptionType1 : pb::IMessage<ComplexOptionType1> {
+    private static readonly pb::MessageParser<ComplexOptionType1> _parser = new pb::MessageParser<ComplexOptionType1>(() => new ComplexOptionType1());
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public static pb::MessageParser<ComplexOptionType1> Parser { get { return _parser; } }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public static pbr::MessageDescriptor Descriptor {
+      get { return global::UnitTest.Issues.TestProtos.UnittestCustomOptionsProto3Reflection.Descriptor.MessageTypes[12]; }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    pbr::MessageDescriptor pb::IMessage.Descriptor {
+      get { return Descriptor; }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public ComplexOptionType1() {
+      OnConstruction();
+    }
+
+    partial void OnConstruction();
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public ComplexOptionType1(ComplexOptionType1 other) : this() {
+      foo_ = other.foo_;
+      foo2_ = other.foo2_;
+      foo3_ = other.foo3_;
+      foo4_ = other.foo4_.Clone();
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public ComplexOptionType1 Clone() {
+      return new ComplexOptionType1(this);
+    }
+
+    /// <summary>Field number for the "foo" field.</summary>
+    public const int FooFieldNumber = 1;
+    private int foo_;
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public int Foo {
+      get { return foo_; }
+      set {
+        foo_ = value;
+      }
+    }
+
+    /// <summary>Field number for the "foo2" field.</summary>
+    public const int Foo2FieldNumber = 2;
+    private int foo2_;
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public int Foo2 {
+      get { return foo2_; }
+      set {
+        foo2_ = value;
+      }
+    }
+
+    /// <summary>Field number for the "foo3" field.</summary>
+    public const int Foo3FieldNumber = 3;
+    private int foo3_;
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public int Foo3 {
+      get { return foo3_; }
+      set {
+        foo3_ = value;
+      }
+    }
+
+    /// <summary>Field number for the "foo4" field.</summary>
+    public const int Foo4FieldNumber = 4;
+    private static readonly pb::FieldCodec<int> _repeated_foo4_codec
+        = pb::FieldCodec.ForInt32(34);
+    private readonly pbc::RepeatedField<int> foo4_ = new pbc::RepeatedField<int>();
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public pbc::RepeatedField<int> Foo4 {
+      get { return foo4_; }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public override bool Equals(object other) {
+      return Equals(other as ComplexOptionType1);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public bool Equals(ComplexOptionType1 other) {
+      if (ReferenceEquals(other, null)) {
+        return false;
+      }
+      if (ReferenceEquals(other, this)) {
+        return true;
+      }
+      if (Foo != other.Foo) return false;
+      if (Foo2 != other.Foo2) return false;
+      if (Foo3 != other.Foo3) return false;
+      if(!foo4_.Equals(other.foo4_)) return false;
+      return true;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public override int GetHashCode() {
+      int hash = 1;
+      if (Foo != 0) hash ^= Foo.GetHashCode();
+      if (Foo2 != 0) hash ^= Foo2.GetHashCode();
+      if (Foo3 != 0) hash ^= Foo3.GetHashCode();
+      hash ^= foo4_.GetHashCode();
+      return hash;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public override string ToString() {
+      return pb::JsonFormatter.ToDiagnosticString(this);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public void WriteTo(pb::CodedOutputStream output) {
+      if (Foo != 0) {
+        output.WriteRawTag(8);
+        output.WriteInt32(Foo);
+      }
+      if (Foo2 != 0) {
+        output.WriteRawTag(16);
+        output.WriteInt32(Foo2);
+      }
+      if (Foo3 != 0) {
+        output.WriteRawTag(24);
+        output.WriteInt32(Foo3);
+      }
+      foo4_.WriteTo(output, _repeated_foo4_codec);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public int CalculateSize() {
+      int size = 0;
+      if (Foo != 0) {
+        size += 1 + pb::CodedOutputStream.ComputeInt32Size(Foo);
+      }
+      if (Foo2 != 0) {
+        size += 1 + pb::CodedOutputStream.ComputeInt32Size(Foo2);
+      }
+      if (Foo3 != 0) {
+        size += 1 + pb::CodedOutputStream.ComputeInt32Size(Foo3);
+      }
+      size += foo4_.CalculateSize(_repeated_foo4_codec);
+      return size;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public void MergeFrom(ComplexOptionType1 other) {
+      if (other == null) {
+        return;
+      }
+      if (other.Foo != 0) {
+        Foo = other.Foo;
+      }
+      if (other.Foo2 != 0) {
+        Foo2 = other.Foo2;
+      }
+      if (other.Foo3 != 0) {
+        Foo3 = other.Foo3;
+      }
+      foo4_.Add(other.foo4_);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public void MergeFrom(pb::CodedInputStream input) {
+      uint tag;
+      while ((tag = input.ReadTag()) != 0) {
+        switch(tag) {
+          default:
+            input.SkipLastField();
+            break;
+          case 8: {
+            Foo = input.ReadInt32();
+            break;
+          }
+          case 16: {
+            Foo2 = input.ReadInt32();
+            break;
+          }
+          case 24: {
+            Foo3 = input.ReadInt32();
+            break;
+          }
+          case 34:
+          case 32: {
+            foo4_.AddEntriesFrom(input, _repeated_foo4_codec);
+            break;
+          }
+        }
+      }
+    }
+
+  }
+
+  public sealed partial class ComplexOptionType2 : pb::IMessage<ComplexOptionType2> {
+    private static readonly pb::MessageParser<ComplexOptionType2> _parser = new pb::MessageParser<ComplexOptionType2>(() => new ComplexOptionType2());
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public static pb::MessageParser<ComplexOptionType2> Parser { get { return _parser; } }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public static pbr::MessageDescriptor Descriptor {
+      get { return global::UnitTest.Issues.TestProtos.UnittestCustomOptionsProto3Reflection.Descriptor.MessageTypes[13]; }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    pbr::MessageDescriptor pb::IMessage.Descriptor {
+      get { return Descriptor; }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public ComplexOptionType2() {
+      OnConstruction();
+    }
+
+    partial void OnConstruction();
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public ComplexOptionType2(ComplexOptionType2 other) : this() {
+      Bar = other.bar_ != null ? other.Bar.Clone() : null;
+      baz_ = other.baz_;
+      Fred = other.fred_ != null ? other.Fred.Clone() : null;
+      barney_ = other.barney_.Clone();
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public ComplexOptionType2 Clone() {
+      return new ComplexOptionType2(this);
+    }
+
+    /// <summary>Field number for the "bar" field.</summary>
+    public const int BarFieldNumber = 1;
+    private global::UnitTest.Issues.TestProtos.ComplexOptionType1 bar_;
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public global::UnitTest.Issues.TestProtos.ComplexOptionType1 Bar {
+      get { return bar_; }
+      set {
+        bar_ = value;
+      }
+    }
+
+    /// <summary>Field number for the "baz" field.</summary>
+    public const int BazFieldNumber = 2;
+    private int baz_;
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public int Baz {
+      get { return baz_; }
+      set {
+        baz_ = value;
+      }
+    }
+
+    /// <summary>Field number for the "fred" field.</summary>
+    public const int FredFieldNumber = 3;
+    private global::UnitTest.Issues.TestProtos.ComplexOptionType2.Types.ComplexOptionType4 fred_;
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public global::UnitTest.Issues.TestProtos.ComplexOptionType2.Types.ComplexOptionType4 Fred {
+      get { return fred_; }
+      set {
+        fred_ = value;
+      }
+    }
+
+    /// <summary>Field number for the "barney" field.</summary>
+    public const int BarneyFieldNumber = 4;
+    private static readonly pb::FieldCodec<global::UnitTest.Issues.TestProtos.ComplexOptionType2.Types.ComplexOptionType4> _repeated_barney_codec
+        = pb::FieldCodec.ForMessage(34, global::UnitTest.Issues.TestProtos.ComplexOptionType2.Types.ComplexOptionType4.Parser);
+    private readonly pbc::RepeatedField<global::UnitTest.Issues.TestProtos.ComplexOptionType2.Types.ComplexOptionType4> barney_ = new pbc::RepeatedField<global::UnitTest.Issues.TestProtos.ComplexOptionType2.Types.ComplexOptionType4>();
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public pbc::RepeatedField<global::UnitTest.Issues.TestProtos.ComplexOptionType2.Types.ComplexOptionType4> Barney {
+      get { return barney_; }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public override bool Equals(object other) {
+      return Equals(other as ComplexOptionType2);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public bool Equals(ComplexOptionType2 other) {
+      if (ReferenceEquals(other, null)) {
+        return false;
+      }
+      if (ReferenceEquals(other, this)) {
+        return true;
+      }
+      if (!object.Equals(Bar, other.Bar)) return false;
+      if (Baz != other.Baz) return false;
+      if (!object.Equals(Fred, other.Fred)) return false;
+      if(!barney_.Equals(other.barney_)) return false;
+      return true;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public override int GetHashCode() {
+      int hash = 1;
+      if (bar_ != null) hash ^= Bar.GetHashCode();
+      if (Baz != 0) hash ^= Baz.GetHashCode();
+      if (fred_ != null) hash ^= Fred.GetHashCode();
+      hash ^= barney_.GetHashCode();
+      return hash;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public override string ToString() {
+      return pb::JsonFormatter.ToDiagnosticString(this);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public void WriteTo(pb::CodedOutputStream output) {
+      if (bar_ != null) {
+        output.WriteRawTag(10);
+        output.WriteMessage(Bar);
+      }
+      if (Baz != 0) {
+        output.WriteRawTag(16);
+        output.WriteInt32(Baz);
+      }
+      if (fred_ != null) {
+        output.WriteRawTag(26);
+        output.WriteMessage(Fred);
+      }
+      barney_.WriteTo(output, _repeated_barney_codec);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public int CalculateSize() {
+      int size = 0;
+      if (bar_ != null) {
+        size += 1 + pb::CodedOutputStream.ComputeMessageSize(Bar);
+      }
+      if (Baz != 0) {
+        size += 1 + pb::CodedOutputStream.ComputeInt32Size(Baz);
+      }
+      if (fred_ != null) {
+        size += 1 + pb::CodedOutputStream.ComputeMessageSize(Fred);
+      }
+      size += barney_.CalculateSize(_repeated_barney_codec);
+      return size;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public void MergeFrom(ComplexOptionType2 other) {
+      if (other == null) {
+        return;
+      }
+      if (other.bar_ != null) {
+        if (bar_ == null) {
+          bar_ = new global::UnitTest.Issues.TestProtos.ComplexOptionType1();
+        }
+        Bar.MergeFrom(other.Bar);
+      }
+      if (other.Baz != 0) {
+        Baz = other.Baz;
+      }
+      if (other.fred_ != null) {
+        if (fred_ == null) {
+          fred_ = new global::UnitTest.Issues.TestProtos.ComplexOptionType2.Types.ComplexOptionType4();
+        }
+        Fred.MergeFrom(other.Fred);
+      }
+      barney_.Add(other.barney_);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public void MergeFrom(pb::CodedInputStream input) {
+      uint tag;
+      while ((tag = input.ReadTag()) != 0) {
+        switch(tag) {
+          default:
+            input.SkipLastField();
+            break;
+          case 10: {
+            if (bar_ == null) {
+              bar_ = new global::UnitTest.Issues.TestProtos.ComplexOptionType1();
+            }
+            input.ReadMessage(bar_);
+            break;
+          }
+          case 16: {
+            Baz = input.ReadInt32();
+            break;
+          }
+          case 26: {
+            if (fred_ == null) {
+              fred_ = new global::UnitTest.Issues.TestProtos.ComplexOptionType2.Types.ComplexOptionType4();
+            }
+            input.ReadMessage(fred_);
+            break;
+          }
+          case 34: {
+            barney_.AddEntriesFrom(input, _repeated_barney_codec);
+            break;
+          }
+        }
+      }
+    }
+
+    #region Nested types
+    /// <summary>Container for nested types declared in the ComplexOptionType2 message type.</summary>
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public static partial class Types {
+      public sealed partial class ComplexOptionType4 : pb::IMessage<ComplexOptionType4> {
+        private static readonly pb::MessageParser<ComplexOptionType4> _parser = new pb::MessageParser<ComplexOptionType4>(() => new ComplexOptionType4());
+        [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+        public static pb::MessageParser<ComplexOptionType4> Parser { get { return _parser; } }
+
+        [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+        public static pbr::MessageDescriptor Descriptor {
+          get { return global::UnitTest.Issues.TestProtos.ComplexOptionType2.Descriptor.NestedTypes[0]; }
+        }
+
+        [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+        pbr::MessageDescriptor pb::IMessage.Descriptor {
+          get { return Descriptor; }
+        }
+
+        [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+        public ComplexOptionType4() {
+          OnConstruction();
+        }
+
+        partial void OnConstruction();
+
+        [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+        public ComplexOptionType4(ComplexOptionType4 other) : this() {
+          waldo_ = other.waldo_;
+        }
+
+        [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+        public ComplexOptionType4 Clone() {
+          return new ComplexOptionType4(this);
+        }
+
+        /// <summary>Field number for the "waldo" field.</summary>
+        public const int WaldoFieldNumber = 1;
+        private int waldo_;
+        [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+        public int Waldo {
+          get { return waldo_; }
+          set {
+            waldo_ = value;
+          }
+        }
+
+        [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+        public override bool Equals(object other) {
+          return Equals(other as ComplexOptionType4);
+        }
+
+        [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+        public bool Equals(ComplexOptionType4 other) {
+          if (ReferenceEquals(other, null)) {
+            return false;
+          }
+          if (ReferenceEquals(other, this)) {
+            return true;
+          }
+          if (Waldo != other.Waldo) return false;
+          return true;
+        }
+
+        [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+        public override int GetHashCode() {
+          int hash = 1;
+          if (Waldo != 0) hash ^= Waldo.GetHashCode();
+          return hash;
+        }
+
+        [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+        public override string ToString() {
+          return pb::JsonFormatter.ToDiagnosticString(this);
+        }
+
+        [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+        public void WriteTo(pb::CodedOutputStream output) {
+          if (Waldo != 0) {
+            output.WriteRawTag(8);
+            output.WriteInt32(Waldo);
+          }
+        }
+
+        [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+        public int CalculateSize() {
+          int size = 0;
+          if (Waldo != 0) {
+            size += 1 + pb::CodedOutputStream.ComputeInt32Size(Waldo);
+          }
+          return size;
+        }
+
+        [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+        public void MergeFrom(ComplexOptionType4 other) {
+          if (other == null) {
+            return;
+          }
+          if (other.Waldo != 0) {
+            Waldo = other.Waldo;
+          }
+        }
+
+        [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+        public void MergeFrom(pb::CodedInputStream input) {
+          uint tag;
+          while ((tag = input.ReadTag()) != 0) {
+            switch(tag) {
+              default:
+                input.SkipLastField();
+                break;
+              case 8: {
+                Waldo = input.ReadInt32();
+                break;
+              }
+            }
+          }
+        }
+
+      }
+
+    }
+    #endregion
+
+  }
+
+  public sealed partial class ComplexOptionType3 : pb::IMessage<ComplexOptionType3> {
+    private static readonly pb::MessageParser<ComplexOptionType3> _parser = new pb::MessageParser<ComplexOptionType3>(() => new ComplexOptionType3());
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public static pb::MessageParser<ComplexOptionType3> Parser { get { return _parser; } }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public static pbr::MessageDescriptor Descriptor {
+      get { return global::UnitTest.Issues.TestProtos.UnittestCustomOptionsProto3Reflection.Descriptor.MessageTypes[14]; }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    pbr::MessageDescriptor pb::IMessage.Descriptor {
+      get { return Descriptor; }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public ComplexOptionType3() {
+      OnConstruction();
+    }
+
+    partial void OnConstruction();
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public ComplexOptionType3(ComplexOptionType3 other) : this() {
+      qux_ = other.qux_;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public ComplexOptionType3 Clone() {
+      return new ComplexOptionType3(this);
+    }
+
+    /// <summary>Field number for the "qux" field.</summary>
+    public const int QuxFieldNumber = 1;
+    private int qux_;
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public int Qux {
+      get { return qux_; }
+      set {
+        qux_ = value;
+      }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public override bool Equals(object other) {
+      return Equals(other as ComplexOptionType3);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public bool Equals(ComplexOptionType3 other) {
+      if (ReferenceEquals(other, null)) {
+        return false;
+      }
+      if (ReferenceEquals(other, this)) {
+        return true;
+      }
+      if (Qux != other.Qux) return false;
+      return true;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public override int GetHashCode() {
+      int hash = 1;
+      if (Qux != 0) hash ^= Qux.GetHashCode();
+      return hash;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public override string ToString() {
+      return pb::JsonFormatter.ToDiagnosticString(this);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public void WriteTo(pb::CodedOutputStream output) {
+      if (Qux != 0) {
+        output.WriteRawTag(8);
+        output.WriteInt32(Qux);
+      }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public int CalculateSize() {
+      int size = 0;
+      if (Qux != 0) {
+        size += 1 + pb::CodedOutputStream.ComputeInt32Size(Qux);
+      }
+      return size;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public void MergeFrom(ComplexOptionType3 other) {
+      if (other == null) {
+        return;
+      }
+      if (other.Qux != 0) {
+        Qux = other.Qux;
+      }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public void MergeFrom(pb::CodedInputStream input) {
+      uint tag;
+      while ((tag = input.ReadTag()) != 0) {
+        switch(tag) {
+          default:
+            input.SkipLastField();
+            break;
+          case 8: {
+            Qux = input.ReadInt32();
+            break;
+          }
+        }
+      }
+    }
+
+  }
+
+  /// <summary>
+  /// Note that we try various different ways of naming the same extension.
+  /// </summary>
+  public sealed partial class VariousComplexOptions : pb::IMessage<VariousComplexOptions> {
+    private static readonly pb::MessageParser<VariousComplexOptions> _parser = new pb::MessageParser<VariousComplexOptions>(() => new VariousComplexOptions());
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public static pb::MessageParser<VariousComplexOptions> Parser { get { return _parser; } }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public static pbr::MessageDescriptor Descriptor {
+      get { return global::UnitTest.Issues.TestProtos.UnittestCustomOptionsProto3Reflection.Descriptor.MessageTypes[15]; }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    pbr::MessageDescriptor pb::IMessage.Descriptor {
+      get { return Descriptor; }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public VariousComplexOptions() {
+      OnConstruction();
+    }
+
+    partial void OnConstruction();
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public VariousComplexOptions(VariousComplexOptions other) : this() {
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public VariousComplexOptions Clone() {
+      return new VariousComplexOptions(this);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public override bool Equals(object other) {
+      return Equals(other as VariousComplexOptions);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public bool Equals(VariousComplexOptions other) {
+      if (ReferenceEquals(other, null)) {
+        return false;
+      }
+      if (ReferenceEquals(other, this)) {
+        return true;
+      }
+      return true;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public override int GetHashCode() {
+      int hash = 1;
+      return hash;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public override string ToString() {
+      return pb::JsonFormatter.ToDiagnosticString(this);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public void WriteTo(pb::CodedOutputStream output) {
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public int CalculateSize() {
+      int size = 0;
+      return size;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public void MergeFrom(VariousComplexOptions other) {
+      if (other == null) {
+        return;
+      }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public void MergeFrom(pb::CodedInputStream input) {
+      uint tag;
+      while ((tag = input.ReadTag()) != 0) {
+        switch(tag) {
+          default:
+            input.SkipLastField();
+            break;
+        }
+      }
+    }
+
+  }
+
+  /// <summary>
+  /// A helper type used to test aggregate option parsing
+  /// </summary>
+  public sealed partial class Aggregate : pb::IMessage<Aggregate> {
+    private static readonly pb::MessageParser<Aggregate> _parser = new pb::MessageParser<Aggregate>(() => new Aggregate());
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public static pb::MessageParser<Aggregate> Parser { get { return _parser; } }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public static pbr::MessageDescriptor Descriptor {
+      get { return global::UnitTest.Issues.TestProtos.UnittestCustomOptionsProto3Reflection.Descriptor.MessageTypes[16]; }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    pbr::MessageDescriptor pb::IMessage.Descriptor {
+      get { return Descriptor; }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public Aggregate() {
+      OnConstruction();
+    }
+
+    partial void OnConstruction();
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public Aggregate(Aggregate other) : this() {
+      i_ = other.i_;
+      s_ = other.s_;
+      Sub = other.sub_ != null ? other.Sub.Clone() : null;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public Aggregate Clone() {
+      return new Aggregate(this);
+    }
+
+    /// <summary>Field number for the "i" field.</summary>
+    public const int IFieldNumber = 1;
+    private int i_;
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public int I {
+      get { return i_; }
+      set {
+        i_ = value;
+      }
+    }
+
+    /// <summary>Field number for the "s" field.</summary>
+    public const int SFieldNumber = 2;
+    private string s_ = "";
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public string S {
+      get { return s_; }
+      set {
+        s_ = pb::ProtoPreconditions.CheckNotNull(value, "value");
+      }
+    }
+
+    /// <summary>Field number for the "sub" field.</summary>
+    public const int SubFieldNumber = 3;
+    private global::UnitTest.Issues.TestProtos.Aggregate sub_;
+    /// <summary>
+    /// A nested object
+    /// </summary>
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public global::UnitTest.Issues.TestProtos.Aggregate Sub {
+      get { return sub_; }
+      set {
+        sub_ = value;
+      }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public override bool Equals(object other) {
+      return Equals(other as Aggregate);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public bool Equals(Aggregate other) {
+      if (ReferenceEquals(other, null)) {
+        return false;
+      }
+      if (ReferenceEquals(other, this)) {
+        return true;
+      }
+      if (I != other.I) return false;
+      if (S != other.S) return false;
+      if (!object.Equals(Sub, other.Sub)) return false;
+      return true;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public override int GetHashCode() {
+      int hash = 1;
+      if (I != 0) hash ^= I.GetHashCode();
+      if (S.Length != 0) hash ^= S.GetHashCode();
+      if (sub_ != null) hash ^= Sub.GetHashCode();
+      return hash;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public override string ToString() {
+      return pb::JsonFormatter.ToDiagnosticString(this);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public void WriteTo(pb::CodedOutputStream output) {
+      if (I != 0) {
+        output.WriteRawTag(8);
+        output.WriteInt32(I);
+      }
+      if (S.Length != 0) {
+        output.WriteRawTag(18);
+        output.WriteString(S);
+      }
+      if (sub_ != null) {
+        output.WriteRawTag(26);
+        output.WriteMessage(Sub);
+      }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public int CalculateSize() {
+      int size = 0;
+      if (I != 0) {
+        size += 1 + pb::CodedOutputStream.ComputeInt32Size(I);
+      }
+      if (S.Length != 0) {
+        size += 1 + pb::CodedOutputStream.ComputeStringSize(S);
+      }
+      if (sub_ != null) {
+        size += 1 + pb::CodedOutputStream.ComputeMessageSize(Sub);
+      }
+      return size;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public void MergeFrom(Aggregate other) {
+      if (other == null) {
+        return;
+      }
+      if (other.I != 0) {
+        I = other.I;
+      }
+      if (other.S.Length != 0) {
+        S = other.S;
+      }
+      if (other.sub_ != null) {
+        if (sub_ == null) {
+          sub_ = new global::UnitTest.Issues.TestProtos.Aggregate();
+        }
+        Sub.MergeFrom(other.Sub);
+      }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public void MergeFrom(pb::CodedInputStream input) {
+      uint tag;
+      while ((tag = input.ReadTag()) != 0) {
+        switch(tag) {
+          default:
+            input.SkipLastField();
+            break;
+          case 8: {
+            I = input.ReadInt32();
+            break;
+          }
+          case 18: {
+            S = input.ReadString();
+            break;
+          }
+          case 26: {
+            if (sub_ == null) {
+              sub_ = new global::UnitTest.Issues.TestProtos.Aggregate();
+            }
+            input.ReadMessage(sub_);
+            break;
+          }
+        }
+      }
+    }
+
+  }
+
+  public sealed partial class AggregateMessage : pb::IMessage<AggregateMessage> {
+    private static readonly pb::MessageParser<AggregateMessage> _parser = new pb::MessageParser<AggregateMessage>(() => new AggregateMessage());
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public static pb::MessageParser<AggregateMessage> Parser { get { return _parser; } }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public static pbr::MessageDescriptor Descriptor {
+      get { return global::UnitTest.Issues.TestProtos.UnittestCustomOptionsProto3Reflection.Descriptor.MessageTypes[17]; }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    pbr::MessageDescriptor pb::IMessage.Descriptor {
+      get { return Descriptor; }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public AggregateMessage() {
+      OnConstruction();
+    }
+
+    partial void OnConstruction();
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public AggregateMessage(AggregateMessage other) : this() {
+      fieldname_ = other.fieldname_;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public AggregateMessage Clone() {
+      return new AggregateMessage(this);
+    }
+
+    /// <summary>Field number for the "fieldname" field.</summary>
+    public const int FieldnameFieldNumber = 1;
+    private int fieldname_;
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public int Fieldname {
+      get { return fieldname_; }
+      set {
+        fieldname_ = value;
+      }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public override bool Equals(object other) {
+      return Equals(other as AggregateMessage);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public bool Equals(AggregateMessage other) {
+      if (ReferenceEquals(other, null)) {
+        return false;
+      }
+      if (ReferenceEquals(other, this)) {
+        return true;
+      }
+      if (Fieldname != other.Fieldname) return false;
+      return true;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public override int GetHashCode() {
+      int hash = 1;
+      if (Fieldname != 0) hash ^= Fieldname.GetHashCode();
+      return hash;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public override string ToString() {
+      return pb::JsonFormatter.ToDiagnosticString(this);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public void WriteTo(pb::CodedOutputStream output) {
+      if (Fieldname != 0) {
+        output.WriteRawTag(8);
+        output.WriteInt32(Fieldname);
+      }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public int CalculateSize() {
+      int size = 0;
+      if (Fieldname != 0) {
+        size += 1 + pb::CodedOutputStream.ComputeInt32Size(Fieldname);
+      }
+      return size;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public void MergeFrom(AggregateMessage other) {
+      if (other == null) {
+        return;
+      }
+      if (other.Fieldname != 0) {
+        Fieldname = other.Fieldname;
+      }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public void MergeFrom(pb::CodedInputStream input) {
+      uint tag;
+      while ((tag = input.ReadTag()) != 0) {
+        switch(tag) {
+          default:
+            input.SkipLastField();
+            break;
+          case 8: {
+            Fieldname = input.ReadInt32();
+            break;
+          }
+        }
+      }
+    }
+
+  }
+
+  /// <summary>
+  /// Test custom options for nested type.
+  /// </summary>
+  public sealed partial class NestedOptionType : pb::IMessage<NestedOptionType> {
+    private static readonly pb::MessageParser<NestedOptionType> _parser = new pb::MessageParser<NestedOptionType>(() => new NestedOptionType());
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public static pb::MessageParser<NestedOptionType> Parser { get { return _parser; } }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public static pbr::MessageDescriptor Descriptor {
+      get { return global::UnitTest.Issues.TestProtos.UnittestCustomOptionsProto3Reflection.Descriptor.MessageTypes[18]; }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    pbr::MessageDescriptor pb::IMessage.Descriptor {
+      get { return Descriptor; }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public NestedOptionType() {
+      OnConstruction();
+    }
+
+    partial void OnConstruction();
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public NestedOptionType(NestedOptionType other) : this() {
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public NestedOptionType Clone() {
+      return new NestedOptionType(this);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public override bool Equals(object other) {
+      return Equals(other as NestedOptionType);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public bool Equals(NestedOptionType other) {
+      if (ReferenceEquals(other, null)) {
+        return false;
+      }
+      if (ReferenceEquals(other, this)) {
+        return true;
+      }
+      return true;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public override int GetHashCode() {
+      int hash = 1;
+      return hash;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public override string ToString() {
+      return pb::JsonFormatter.ToDiagnosticString(this);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public void WriteTo(pb::CodedOutputStream output) {
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public int CalculateSize() {
+      int size = 0;
+      return size;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public void MergeFrom(NestedOptionType other) {
+      if (other == null) {
+        return;
+      }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public void MergeFrom(pb::CodedInputStream input) {
+      uint tag;
+      while ((tag = input.ReadTag()) != 0) {
+        switch(tag) {
+          default:
+            input.SkipLastField();
+            break;
+        }
+      }
+    }
+
+    #region Nested types
+    /// <summary>Container for nested types declared in the NestedOptionType message type.</summary>
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public static partial class Types {
+      public enum NestedEnum {
+        [pbr::OriginalName("UNSPECIFIED")] Unspecified = 0,
+        [pbr::OriginalName("NESTED_ENUM_VALUE")] Value = 1,
+      }
+
+      public sealed partial class NestedMessage : pb::IMessage<NestedMessage> {
+        private static readonly pb::MessageParser<NestedMessage> _parser = new pb::MessageParser<NestedMessage>(() => new NestedMessage());
+        [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+        public static pb::MessageParser<NestedMessage> Parser { get { return _parser; } }
+
+        [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+        public static pbr::MessageDescriptor Descriptor {
+          get { return global::UnitTest.Issues.TestProtos.NestedOptionType.Descriptor.NestedTypes[0]; }
+        }
+
+        [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+        pbr::MessageDescriptor pb::IMessage.Descriptor {
+          get { return Descriptor; }
+        }
+
+        [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+        public NestedMessage() {
+          OnConstruction();
+        }
+
+        partial void OnConstruction();
+
+        [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+        public NestedMessage(NestedMessage other) : this() {
+          nestedField_ = other.nestedField_;
+        }
+
+        [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+        public NestedMessage Clone() {
+          return new NestedMessage(this);
+        }
+
+        /// <summary>Field number for the "nested_field" field.</summary>
+        public const int NestedFieldFieldNumber = 1;
+        private int nestedField_;
+        [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+        public int NestedField {
+          get { return nestedField_; }
+          set {
+            nestedField_ = value;
+          }
+        }
+
+        [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+        public override bool Equals(object other) {
+          return Equals(other as NestedMessage);
+        }
+
+        [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+        public bool Equals(NestedMessage other) {
+          if (ReferenceEquals(other, null)) {
+            return false;
+          }
+          if (ReferenceEquals(other, this)) {
+            return true;
+          }
+          if (NestedField != other.NestedField) return false;
+          return true;
+        }
+
+        [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+        public override int GetHashCode() {
+          int hash = 1;
+          if (NestedField != 0) hash ^= NestedField.GetHashCode();
+          return hash;
+        }
+
+        [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+        public override string ToString() {
+          return pb::JsonFormatter.ToDiagnosticString(this);
+        }
+
+        [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+        public void WriteTo(pb::CodedOutputStream output) {
+          if (NestedField != 0) {
+            output.WriteRawTag(8);
+            output.WriteInt32(NestedField);
+          }
+        }
+
+        [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+        public int CalculateSize() {
+          int size = 0;
+          if (NestedField != 0) {
+            size += 1 + pb::CodedOutputStream.ComputeInt32Size(NestedField);
+          }
+          return size;
+        }
+
+        [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+        public void MergeFrom(NestedMessage other) {
+          if (other == null) {
+            return;
+          }
+          if (other.NestedField != 0) {
+            NestedField = other.NestedField;
+          }
+        }
+
+        [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+        public void MergeFrom(pb::CodedInputStream input) {
+          uint tag;
+          while ((tag = input.ReadTag()) != 0) {
+            switch(tag) {
+              default:
+                input.SkipLastField();
+                break;
+              case 8: {
+                NestedField = input.ReadInt32();
+                break;
+              }
+            }
+          }
+        }
+
+      }
+
+    }
+    #endregion
+
+  }
+
+  #endregion
+
+}
+
+#endregion Designer generated code

+ 390 - 0
csharp/src/Google.Protobuf/Reflection/CustomOptions.cs

@@ -0,0 +1,390 @@
+#region Copyright notice and license
+// Protocol Buffers - Google's data interchange format
+// Copyright 2017 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#endregion
+
+using System;
+using System.Collections.Generic;
+
+namespace Google.Protobuf.Reflection
+{
+    /// <summary>
+    /// Container for a set of custom options specified within a message, field etc.
+    /// </summary>
+    /// <remarks>
+    /// <para>
+    /// This type is publicly immutable, but internally mutable. It is only populated
+    /// by the descriptor parsing code - by the time any user code is able to see an instance,
+    /// it will be fully initialized.
+    /// </para>
+    /// <para>
+    /// If an option is requested using the incorrect method, an answer may still be returned: all
+    /// of the numeric types are represented internally using 64-bit integers, for example. It is up to
+    /// the caller to ensure that they make the appropriate method call for the option they're interested in.
+    /// Note that enum options are simply stored as integers, so the value should be fetched using
+    /// <see cref="TryGetInt32(int, out int)"/> and then cast appropriately.
+    /// </para>
+    /// <para>
+    /// Repeated options are currently not supported. Asking for a single value of an option
+    /// which was actually repeated will return the last value, except for message types where
+    /// all the set values are merged together.
+    /// </para>
+    /// </remarks>
+    public sealed class CustomOptions
+    {
+        /// <summary>
+        /// Singleton for all descriptors with an empty set of options.
+        /// </summary>
+        internal static readonly CustomOptions Empty = new CustomOptions();
+        
+        /// <summary>
+        /// A sequence of values per field. This needs to be per field rather than per tag to allow correct deserialization
+        /// of repeated fields which could be "int, ByteString, int" - unlikely as that is. The fact that values are boxed
+        /// is unfortunate; we might be able to use a struct instead, and we could combine uint and ulong values.
+        /// </summary>
+        private readonly Dictionary<int, List<FieldValue>> valuesByField = new Dictionary<int, List<FieldValue>>();
+
+        private CustomOptions() { }
+
+        /// <summary>
+        /// Retrieves a Boolean value for the specified option field.
+        /// </summary>
+        /// <param name="field">The field to fetch the value for.</param>
+        /// <param name="value">The output variable to populate.</param>
+        /// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns>
+        public bool TryGetBool(int field, out bool value)
+        {
+            ulong? tmp = GetLastNumericValue(field);
+            value = tmp == 1UL;
+            return tmp != null;
+        }
+
+        /// <summary>
+        /// Retrieves a signed 32-bit integer value for the specified option field.
+        /// </summary>
+        /// <param name="field">The field to fetch the value for.</param>
+        /// <param name="value">The output variable to populate.</param>
+        /// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns>
+        public bool TryGetInt32(int field, out int value)
+        {
+            ulong? tmp = GetLastNumericValue(field);
+            value = (int) tmp.GetValueOrDefault();
+            return tmp != null;
+        }
+
+        /// <summary>
+        /// Retrieves a signed 64-bit integer value for the specified option field.
+        /// </summary>
+        /// <param name="field">The field to fetch the value for.</param>
+        /// <param name="value">The output variable to populate.</param>
+        /// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns>
+        public bool TryGetInt64(int field, out long value)
+        {
+            ulong? tmp = GetLastNumericValue(field);
+            value = (long) tmp.GetValueOrDefault();
+            return tmp != null;
+        }
+
+        /// <summary>
+        /// Retrieves an unsigned 32-bit integer value for the specified option field,
+        /// assuming a fixed-length representation.
+        /// </summary>
+        /// <param name="field">The field to fetch the value for.</param>
+        /// <param name="value">The output variable to populate.</param>
+        /// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns>
+        public bool TryGetFixed32(int field, out uint value) => TryGetUInt32(field, out value);
+
+        /// <summary>
+        /// Retrieves an unsigned 64-bit integer value for the specified option field,
+        /// assuming a fixed-length representation.
+        /// </summary>
+        /// <param name="field">The field to fetch the value for.</param>
+        /// <param name="value">The output variable to populate.</param>
+        /// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns>
+        public bool TryGetFixed64(int field, out ulong value) => TryGetUInt64(field, out value);
+
+        /// <summary>
+        /// Retrieves a signed 32-bit integer value for the specified option field,
+        /// assuming a fixed-length representation.
+        /// </summary>
+        /// <param name="field">The field to fetch the value for.</param>
+        /// <param name="value">The output variable to populate.</param>
+        /// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns>
+        public bool TryGetSFixed32(int field, out int value) => TryGetInt32(field, out value);
+
+        /// <summary>
+        /// Retrieves a signed 64-bit integer value for the specified option field,
+        /// assuming a fixed-length representation.
+        /// </summary>
+        /// <param name="field">The field to fetch the value for.</param>
+        /// <param name="value">The output variable to populate.</param>
+        /// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns>
+        public bool TryGetSFixed64(int field, out long value) => TryGetInt64(field, out value);
+        
+        /// <summary>
+        /// Retrieves a signed 32-bit integer value for the specified option field,
+        /// assuming a zigzag encoding.
+        /// </summary>
+        /// <param name="field">The field to fetch the value for.</param>
+        /// <param name="value">The output variable to populate.</param>
+        /// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns>
+        public bool TryGetSInt32(int field, out int value)
+        {
+            ulong? tmp = GetLastNumericValue(field);
+            value = CodedInputStream.DecodeZigZag32((uint) tmp.GetValueOrDefault());
+            return tmp != null;
+        }
+
+        /// <summary>
+        /// Retrieves a signed 64-bit integer value for the specified option field,
+        /// assuming a zigzag encoding.
+        /// </summary>
+        /// <param name="field">The field to fetch the value for.</param>
+        /// <param name="value">The output variable to populate.</param>
+        /// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns>
+        public bool TryGetSInt64(int field, out long value)
+        {
+            ulong? tmp = GetLastNumericValue(field);
+            value = CodedInputStream.DecodeZigZag64(tmp.GetValueOrDefault());
+            return tmp != null;
+        }
+
+        /// <summary>
+        /// Retrieves an unsigned 32-bit integer value for the specified option field.
+        /// </summary>
+        /// <param name="field">The field to fetch the value for.</param>
+        /// <param name="value">The output variable to populate.</param>
+        /// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns>
+        public bool TryGetUInt32(int field, out uint value)
+        {
+            ulong? tmp = GetLastNumericValue(field);
+            value = (uint) tmp.GetValueOrDefault();
+            return tmp != null;
+        }
+
+        /// <summary>
+        /// Retrieves an unsigned 64-bit integer value for the specified option field.
+        /// </summary>
+        /// <param name="field">The field to fetch the value for.</param>
+        /// <param name="value">The output variable to populate.</param>
+        /// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns>
+        public bool TryGetUInt64(int field, out ulong value)
+        {
+            ulong? tmp = GetLastNumericValue(field);
+            value = tmp.GetValueOrDefault();
+            return tmp != null;
+        }
+
+        /// <summary>
+        /// Retrieves a 32-bit floating point value for the specified option field.
+        /// </summary>
+        /// <param name="field">The field to fetch the value for.</param>
+        /// <param name="value">The output variable to populate.</param>
+        /// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns>
+        public bool TryGetFloat(int field, out float value)
+        {
+            ulong? tmp = GetLastNumericValue(field);
+            int int32 = (int) tmp.GetValueOrDefault();
+            byte[] bytes = BitConverter.GetBytes(int32);
+            value = BitConverter.ToSingle(bytes, 0);
+            return tmp != null;
+        }
+
+        /// <summary>
+        /// Retrieves a 64-bit floating point value for the specified option field.
+        /// </summary>
+        /// <param name="field">The field to fetch the value for.</param>
+        /// <param name="value">The output variable to populate.</param>
+        /// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns>
+        public bool TryGetDouble(int field, out double value)
+        {
+            ulong? tmp = GetLastNumericValue(field);
+            value = BitConverter.Int64BitsToDouble((long) tmp.GetValueOrDefault());
+            return tmp != null;
+        }
+
+        /// <summary>
+        /// Retrieves a string value for the specified option field.
+        /// </summary>
+        /// <param name="field">The field to fetch the value for.</param>
+        /// <param name="value">The output variable to populate.</param>
+        /// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns>
+        public bool TryGetString(int field, out string value)
+        {
+            ByteString bytes = GetLastByteStringValue(field);
+            value = bytes?.ToStringUtf8();
+            return bytes != null;
+        }
+
+        /// <summary>
+        /// Retrieves a bytes value for the specified option field.
+        /// </summary>
+        /// <param name="field">The field to fetch the value for.</param>
+        /// <param name="value">The output variable to populate.</param>
+        /// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns>
+        public bool TryGetBytes(int field, out ByteString value)
+        {
+            ByteString bytes = GetLastByteStringValue(field);
+            value = bytes;
+            return bytes != null;
+        }
+
+        /// <summary>
+        /// Retrieves a message value for the specified option field.
+        /// </summary>
+        /// <param name="field">The field to fetch the value for.</param>
+        /// <param name="value">The output variable to populate.</param>
+        /// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns>
+        public bool TryGetMessage<T>(int field, out T value) where T : class, IMessage, new()
+        {
+            value = null;
+            List<FieldValue> values;
+            if (!valuesByField.TryGetValue(field, out values))
+            {
+                return false;
+            }
+            foreach (FieldValue fieldValue in values)
+            {
+                if (fieldValue.ByteString != null)
+                {
+                    if (value == null)
+                    {
+                        value = new T();
+                    }
+                    value.MergeFrom(fieldValue.ByteString);
+                }
+            }
+            return value != null;
+        }
+
+        private ulong? GetLastNumericValue(int field)
+        {
+            List<FieldValue> values;
+            if (!valuesByField.TryGetValue(field, out values))
+            {
+                return null;
+            }
+            for (int i = values.Count - 1; i >= 0; i--)
+            {
+                // A non-bytestring value is a numeric value
+                if (values[i].ByteString == null)
+                {
+                    return values[i].Number;
+                }
+            }
+            return null;
+        }
+
+        private ByteString GetLastByteStringValue(int field)
+        {
+            List<FieldValue> values;
+            if (!valuesByField.TryGetValue(field, out values))
+            {
+                return null;
+            }
+            for (int i = values.Count - 1; i >= 0; i--)
+            {
+                if (values[i].ByteString != null)
+                {
+                    return values[i].ByteString;
+                }
+            }
+            return null;
+        }
+
+        /// <summary>
+        /// Reads an unknown field, either parsing it and storing it or skipping it.
+        /// </summary>
+        /// <remarks>
+        /// If the current set of options is empty and we manage to read a field, a new set of options
+        /// will be created and returned. Otherwise, the return value is <c>this</c>. This allows
+        /// us to start with a singleton empty set of options and just create new ones where necessary.
+        /// </remarks>
+        /// <param name="input">Input stream to read from. </param>
+        /// <returns>The resulting set of custom options, either <c>this</c> or a new set.</returns>
+        internal CustomOptions ReadOrSkipUnknownField(CodedInputStream input)
+        {
+            var tag = input.LastTag;
+            var field = WireFormat.GetTagFieldNumber(tag);
+            switch (WireFormat.GetTagWireType(tag))
+            {
+                case WireFormat.WireType.LengthDelimited:
+                    return AddValue(field, new FieldValue(input.ReadBytes()));
+                case WireFormat.WireType.Fixed32:
+                    return AddValue(field, new FieldValue(input.ReadFixed32()));
+                case WireFormat.WireType.Fixed64:
+                    return AddValue(field, new FieldValue(input.ReadFixed64()));
+                case WireFormat.WireType.Varint:
+                    return AddValue(field, new FieldValue(input.ReadRawVarint64()));
+                // For StartGroup, EndGroup or any wire format we don't understand,
+                // just use the normal behavior (call SkipLastField).
+                default:
+                    input.SkipLastField();
+                    return this;
+            }
+        }
+
+        private CustomOptions AddValue(int field, FieldValue value)
+        {
+            var ret = valuesByField.Count == 0 ? new CustomOptions() : this;
+            List<FieldValue> valuesForField;
+            if (!ret.valuesByField.TryGetValue(field, out valuesForField))
+            {
+                // Expect almost all 
+                valuesForField = new List<FieldValue>(1);
+                ret.valuesByField[field] = valuesForField;
+            }
+            valuesForField.Add(value);
+            return ret;
+        }
+
+        /// <summary>
+        /// All field values can be stored as a byte string or a 64-bit integer.
+        /// This struct avoids unnecessary boxing.
+        /// </summary>
+        private struct FieldValue
+        {
+            internal ulong Number { get; }
+            internal ByteString ByteString { get; }
+
+            internal FieldValue(ulong number)
+            {
+                Number = number;
+                ByteString = null;
+            }
+
+            internal FieldValue(ByteString byteString)
+            {
+                Number = 0;
+                ByteString = byteString;
+            }
+        }
+    }
+}

+ 24 - 8
csharp/src/Google.Protobuf/Reflection/Descriptor.cs

@@ -2779,6 +2779,8 @@ namespace Google.Protobuf.Reflection {
       get { return Descriptor; }
       get { return Descriptor; }
     }
     }
 
 
+    internal CustomOptions CustomOptions{ get; private set; } = CustomOptions.Empty;
+
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public FileOptions() {
     public FileOptions() {
       OnConstruction();
       OnConstruction();
@@ -3299,7 +3301,7 @@ namespace Google.Protobuf.Reflection {
       while ((tag = input.ReadTag()) != 0) {
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
         switch(tag) {
           default:
           default:
-            input.SkipLastField();
+            CustomOptions = CustomOptions.ReadOrSkipUnknownField(input);
             break;
             break;
           case 10: {
           case 10: {
             JavaPackage = input.ReadString();
             JavaPackage = input.ReadString();
@@ -3411,6 +3413,8 @@ namespace Google.Protobuf.Reflection {
       get { return Descriptor; }
       get { return Descriptor; }
     }
     }
 
 
+    internal CustomOptions CustomOptions{ get; private set; } = CustomOptions.Empty;
+
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public MessageOptions() {
     public MessageOptions() {
       OnConstruction();
       OnConstruction();
@@ -3646,7 +3650,7 @@ namespace Google.Protobuf.Reflection {
       while ((tag = input.ReadTag()) != 0) {
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
         switch(tag) {
           default:
           default:
-            input.SkipLastField();
+            CustomOptions = CustomOptions.ReadOrSkipUnknownField(input);
             break;
             break;
           case 8: {
           case 8: {
             MessageSetWireFormat = input.ReadBool();
             MessageSetWireFormat = input.ReadBool();
@@ -3689,6 +3693,8 @@ namespace Google.Protobuf.Reflection {
       get { return Descriptor; }
       get { return Descriptor; }
     }
     }
 
 
+    internal CustomOptions CustomOptions{ get; private set; } = CustomOptions.Empty;
+
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public FieldOptions() {
     public FieldOptions() {
       OnConstruction();
       OnConstruction();
@@ -3980,7 +3986,7 @@ namespace Google.Protobuf.Reflection {
       while ((tag = input.ReadTag()) != 0) {
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
         switch(tag) {
           default:
           default:
-            input.SkipLastField();
+            CustomOptions = CustomOptions.ReadOrSkipUnknownField(input);
             break;
             break;
           case 8: {
           case 8: {
             ctype_ = (global::Google.Protobuf.Reflection.FieldOptions.Types.CType) input.ReadEnum();
             ctype_ = (global::Google.Protobuf.Reflection.FieldOptions.Types.CType) input.ReadEnum();
@@ -4062,6 +4068,8 @@ namespace Google.Protobuf.Reflection {
       get { return Descriptor; }
       get { return Descriptor; }
     }
     }
 
 
+    internal CustomOptions CustomOptions{ get; private set; } = CustomOptions.Empty;
+
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public OneofOptions() {
     public OneofOptions() {
       OnConstruction();
       OnConstruction();
@@ -4147,7 +4155,7 @@ namespace Google.Protobuf.Reflection {
       while ((tag = input.ReadTag()) != 0) {
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
         switch(tag) {
           default:
           default:
-            input.SkipLastField();
+            CustomOptions = CustomOptions.ReadOrSkipUnknownField(input);
             break;
             break;
           case 7994: {
           case 7994: {
             uninterpretedOption_.AddEntriesFrom(input, _repeated_uninterpretedOption_codec);
             uninterpretedOption_.AddEntriesFrom(input, _repeated_uninterpretedOption_codec);
@@ -4174,6 +4182,8 @@ namespace Google.Protobuf.Reflection {
       get { return Descriptor; }
       get { return Descriptor; }
     }
     }
 
 
+    internal CustomOptions CustomOptions{ get; private set; } = CustomOptions.Empty;
+
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public EnumOptions() {
     public EnumOptions() {
       OnConstruction();
       OnConstruction();
@@ -4317,7 +4327,7 @@ namespace Google.Protobuf.Reflection {
       while ((tag = input.ReadTag()) != 0) {
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
         switch(tag) {
           default:
           default:
-            input.SkipLastField();
+            CustomOptions = CustomOptions.ReadOrSkipUnknownField(input);
             break;
             break;
           case 16: {
           case 16: {
             AllowAlias = input.ReadBool();
             AllowAlias = input.ReadBool();
@@ -4352,6 +4362,8 @@ namespace Google.Protobuf.Reflection {
       get { return Descriptor; }
       get { return Descriptor; }
     }
     }
 
 
+    internal CustomOptions CustomOptions{ get; private set; } = CustomOptions.Empty;
+
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public EnumValueOptions() {
     public EnumValueOptions() {
       OnConstruction();
       OnConstruction();
@@ -4467,7 +4479,7 @@ namespace Google.Protobuf.Reflection {
       while ((tag = input.ReadTag()) != 0) {
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
         switch(tag) {
           default:
           default:
-            input.SkipLastField();
+            CustomOptions = CustomOptions.ReadOrSkipUnknownField(input);
             break;
             break;
           case 8: {
           case 8: {
             Deprecated = input.ReadBool();
             Deprecated = input.ReadBool();
@@ -4498,6 +4510,8 @@ namespace Google.Protobuf.Reflection {
       get { return Descriptor; }
       get { return Descriptor; }
     }
     }
 
 
+    internal CustomOptions CustomOptions{ get; private set; } = CustomOptions.Empty;
+
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public ServiceOptions() {
     public ServiceOptions() {
       OnConstruction();
       OnConstruction();
@@ -4613,7 +4627,7 @@ namespace Google.Protobuf.Reflection {
       while ((tag = input.ReadTag()) != 0) {
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
         switch(tag) {
           default:
           default:
-            input.SkipLastField();
+            CustomOptions = CustomOptions.ReadOrSkipUnknownField(input);
             break;
             break;
           case 264: {
           case 264: {
             Deprecated = input.ReadBool();
             Deprecated = input.ReadBool();
@@ -4644,6 +4658,8 @@ namespace Google.Protobuf.Reflection {
       get { return Descriptor; }
       get { return Descriptor; }
     }
     }
 
 
+    internal CustomOptions CustomOptions{ get; private set; } = CustomOptions.Empty;
+
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public MethodOptions() {
     public MethodOptions() {
       OnConstruction();
       OnConstruction();
@@ -4783,7 +4799,7 @@ namespace Google.Protobuf.Reflection {
       while ((tag = input.ReadTag()) != 0) {
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
         switch(tag) {
           default:
           default:
-            input.SkipLastField();
+            CustomOptions = CustomOptions.ReadOrSkipUnknownField(input);
             break;
             break;
           case 264: {
           case 264: {
             Deprecated = input.ReadBool();
             Deprecated = input.ReadBool();

+ 5 - 0
csharp/src/Google.Protobuf/Reflection/EnumDescriptor.cs

@@ -112,5 +112,10 @@ namespace Google.Protobuf.Reflection
         {
         {
             return File.DescriptorPool.FindSymbol<EnumValueDescriptor>(FullName + "." + name);
             return File.DescriptorPool.FindSymbol<EnumValueDescriptor>(FullName + "." + name);
         }
         }
+
+        /// <summary>
+        /// The (possibly empty) set of custom options for this enum.
+        /// </summary>
+        public CustomOptions CustomOptions => Proto.Options?.CustomOptions ?? CustomOptions.Empty;
     }
     }
 }
 }

+ 5 - 0
csharp/src/Google.Protobuf/Reflection/EnumValueDescriptor.cs

@@ -66,5 +66,10 @@ namespace Google.Protobuf.Reflection
         /// Returns the enum descriptor that this value is part of.
         /// Returns the enum descriptor that this value is part of.
         /// </summary>
         /// </summary>
         public EnumDescriptor EnumDescriptor { get { return enumDescriptor; } }
         public EnumDescriptor EnumDescriptor { get { return enumDescriptor; } }
+
+        /// <summary>
+        /// The (possibly empty) set of custom options for this enum value.
+        /// </summary>
+        public CustomOptions CustomOptions => Proto.Options?.CustomOptions ?? CustomOptions.Empty;
     }
     }
 }
 }

+ 5 - 0
csharp/src/Google.Protobuf/Reflection/FieldDescriptor.cs

@@ -250,6 +250,11 @@ namespace Google.Protobuf.Reflection
             }
             }
         }
         }
 
 
+        /// <summary>
+        /// The (possibly empty) set of custom options for this field.
+        /// </summary>
+        public CustomOptions CustomOptions => Proto.Options?.CustomOptions ?? CustomOptions.Empty;
+
         /// <summary>
         /// <summary>
         /// Look up and cross-link all field types etc.
         /// Look up and cross-link all field types etc.
         /// </summary>
         /// </summary>

+ 5 - 0
csharp/src/Google.Protobuf/Reflection/FileDescriptor.cs

@@ -329,5 +329,10 @@ namespace Google.Protobuf.Reflection
         /// The file descriptor for <c>descriptor.proto</c>.
         /// The file descriptor for <c>descriptor.proto</c>.
         /// </value>
         /// </value>
         public static FileDescriptor DescriptorProtoFileDescriptor { get { return DescriptorReflection.Descriptor; } }
         public static FileDescriptor DescriptorProtoFileDescriptor { get { return DescriptorReflection.Descriptor; } }
+
+        /// <summary>
+        /// The (possibly empty) set of custom options for this file.
+        /// </summary>
+        public CustomOptions CustomOptions => Proto.Options?.CustomOptions ?? CustomOptions.Empty;
     }
     }
 }
 }

+ 5 - 0
csharp/src/Google.Protobuf/Reflection/MessageDescriptor.cs

@@ -220,6 +220,11 @@ namespace Google.Protobuf.Reflection
         public T FindDescriptor<T>(string name)  where T : class, IDescriptor =>
         public T FindDescriptor<T>(string name)  where T : class, IDescriptor =>
             File.DescriptorPool.FindSymbol<T>(FullName + "." + name);
             File.DescriptorPool.FindSymbol<T>(FullName + "." + name);
 
 
+        /// <summary>
+        /// The (possibly empty) set of custom options for this message.
+        /// </summary>
+        public CustomOptions CustomOptions => Proto.Options?.CustomOptions ?? CustomOptions.Empty;
+
         /// <summary>
         /// <summary>
         /// Looks up and cross-links all fields and nested types.
         /// Looks up and cross-links all fields and nested types.
         /// </summary>
         /// </summary>

+ 5 - 0
csharp/src/Google.Protobuf/Reflection/MethodDescriptor.cs

@@ -67,6 +67,11 @@ namespace Google.Protobuf.Reflection
         /// </value>
         /// </value>
         public bool IsServerStreaming { get { return proto.ServerStreaming; } }
         public bool IsServerStreaming { get { return proto.ServerStreaming; } }
 
 
+        /// <summary>
+        /// The (possibly empty) set of custom options for this method.
+        /// </summary>
+        public CustomOptions CustomOptions => Proto.Options?.CustomOptions ?? CustomOptions.Empty;
+
         internal MethodDescriptor(MethodDescriptorProto proto, FileDescriptor file,
         internal MethodDescriptor(MethodDescriptorProto proto, FileDescriptor file,
                                   ServiceDescriptor parent, int index)
                                   ServiceDescriptor parent, int index)
             : base(file, parent.FullName + "." + proto.Name, index)
             : base(file, parent.FullName + "." + proto.Name, index)

+ 5 - 0
csharp/src/Google.Protobuf/Reflection/OneofDescriptor.cs

@@ -90,6 +90,11 @@ namespace Google.Protobuf.Reflection
         /// </value>
         /// </value>
         public OneofAccessor Accessor { get { return accessor; } }
         public OneofAccessor Accessor { get { return accessor; } }
 
 
+        /// <summary>
+        /// The (possibly empty) set of custom options for this oneof.
+        /// </summary>
+        public CustomOptions CustomOptions => proto.Options?.CustomOptions ?? CustomOptions.Empty;
+
         internal void CrossLink()
         internal void CrossLink()
         {
         {
             List<FieldDescriptor> fieldCollection = new List<FieldDescriptor>();
             List<FieldDescriptor> fieldCollection = new List<FieldDescriptor>();

+ 5 - 0
csharp/src/Google.Protobuf/Reflection/ServiceDescriptor.cs

@@ -78,6 +78,11 @@ namespace Google.Protobuf.Reflection
             return File.DescriptorPool.FindSymbol<MethodDescriptor>(FullName + "." + name);
             return File.DescriptorPool.FindSymbol<MethodDescriptor>(FullName + "." + name);
         }
         }
 
 
+        /// <summary>
+        /// The (possibly empty) set of custom options for this service.
+        /// </summary>
+        public CustomOptions CustomOptions => Proto.Options?.CustomOptions ?? CustomOptions.Empty;
+
         internal void CrossLink()
         internal void CrossLink()
         {
         {
             foreach (MethodDescriptor method in methods)
             foreach (MethodDescriptor method in methods)

+ 16 - 0
src/google/protobuf/compiler/csharp/csharp_helpers.h

@@ -120,6 +120,22 @@ inline bool IsDescriptorProto(const FileDescriptor* descriptor) {
   return descriptor->name() == "google/protobuf/descriptor.proto";
   return descriptor->name() == "google/protobuf/descriptor.proto";
 }
 }
 
 
+// Determines whether the given message is an options message within descriptor.proto.
+inline bool IsDescriptorOptionMessage(const Descriptor* descriptor) {
+  if (!IsDescriptorProto(descriptor->file())) {
+    return false;
+  }
+  const string name = descriptor->full_name();
+  return name == "google.protobuf.FileOptions" ||
+      name == "google.protobuf.MessageOptions" ||
+      name == "google.protobuf.FieldOptions" ||
+      name == "google.protobuf.OneofOptions" ||
+      name == "google.protobuf.EnumOptions" ||
+      name == "google.protobuf.EnumValueOptions" ||
+      name == "google.protobuf.ServiceOptions" ||
+      name == "google.protobuf.MethodOptions";
+}
+
 inline bool IsWrapperType(const FieldDescriptor* descriptor) {
 inline bool IsWrapperType(const FieldDescriptor* descriptor) {
   return descriptor->type() == FieldDescriptor::TYPE_MESSAGE &&
   return descriptor->type() == FieldDescriptor::TYPE_MESSAGE &&
       descriptor->message_type()->file()->name() == "google/protobuf/wrappers.proto";
       descriptor->message_type()->file()->name() == "google/protobuf/wrappers.proto";

+ 18 - 4
src/google/protobuf/compiler/csharp/csharp_message.cc

@@ -151,6 +151,12 @@ void MessageGenerator::Generate(io::Printer* printer) {
     "  get { return Descriptor; }\n"
     "  get { return Descriptor; }\n"
     "}\n"
     "}\n"
     "\n");
     "\n");
+  // CustomOptions property, only for options messages
+  if (IsDescriptorOptionMessage(descriptor_)) {
+    printer->Print(
+      "internal CustomOptions CustomOptions{ get; private set; } = CustomOptions.Empty;\n"
+       "\n");
+  }
 
 
   // Parameterless constructor and partial OnConstruction method.
   // Parameterless constructor and partial OnConstruction method.
   WriteGeneratedCodeAttributes(printer);
   WriteGeneratedCodeAttributes(printer);
@@ -475,10 +481,18 @@ void MessageGenerator::GenerateMergingMethods(io::Printer* printer) {
     "  switch(tag) {\n");
     "  switch(tag) {\n");
   printer->Indent();
   printer->Indent();
   printer->Indent();
   printer->Indent();
-  printer->Print(
-    "default:\n"
-    "  input.SkipLastField();\n" // We're not storing the data, but we still need to consume it.
-    "  break;\n");
+  // Option messages need to store unknown fields so that options can be parsed later.
+  if (IsDescriptorOptionMessage(descriptor_)) {
+	printer->Print(
+      "default:\n"
+      "  CustomOptions = CustomOptions.ReadOrSkipUnknownField(input);\n"
+      "  break;\n");
+  } else {
+    printer->Print(
+      "default:\n"
+      "  input.SkipLastField();\n" // We're not storing the data, but we still need to consume it.
+      "  break;\n");
+  }
   for (int i = 0; i < fields_by_number().size(); i++) {
   for (int i = 0; i < fields_by_number().size(); i++) {
     const FieldDescriptor* field = fields_by_number()[i];
     const FieldDescriptor* field = fields_by_number()[i];
     internal::WireFormatLite::WireType wt =
     internal::WireFormatLite::WireType wt =