diff --git a/csharp/src/Google.Protobuf.Test/Reflection/FieldAccessTest.cs b/csharp/src/Google.Protobuf.Test/Reflection/FieldAccessTest.cs index fcfff4f7a..dd753e8df 100644 --- a/csharp/src/Google.Protobuf.Test/Reflection/FieldAccessTest.cs +++ b/csharp/src/Google.Protobuf.Test/Reflection/FieldAccessTest.cs @@ -101,7 +101,7 @@ namespace Google.Protobuf.Reflection { IMessage message = SampleMessages.CreateFullTestAllTypes(); var fields = message.Descriptor.Fields; - Assert.Throws(() => fields[TestProtos.TestAllTypes.SingleBoolFieldNumber].Accessor.HasValue(message)); + Assert.Throws(() => (fields[TestProtos.TestAllTypes.SingleBoolFieldNumber].Accessor as IFieldPresenceAccessor).HasValue(message)); } [Test] @@ -109,7 +109,7 @@ namespace Google.Protobuf.Reflection { IMessage message = new Proto2.TestAllTypes(); var fields = message.Descriptor.Fields; - var accessor = fields[Proto2.TestAllTypes.OptionalBoolFieldNumber].Accessor; + var accessor = fields[Proto2.TestAllTypes.OptionalBoolFieldNumber].Accessor as IFieldPresenceAccessor; Assert.False(accessor.HasValue(message)); diff --git a/csharp/src/Google.Protobuf/MessageExtensions.cs b/csharp/src/Google.Protobuf/MessageExtensions.cs index d297a118c..986c3798d 100644 --- a/csharp/src/Google.Protobuf/MessageExtensions.cs +++ b/csharp/src/Google.Protobuf/MessageExtensions.cs @@ -148,12 +148,12 @@ namespace Google.Protobuf /// public static bool IsInitialized(this IMessage message) { - if (message.Descriptor.File.Proto.Syntax == "proto3") + if (message.Descriptor.File.Syntax == Syntax.Proto3) { return true; } - if (!message.Descriptor.GetIsExtensionsInitialized(message)) + if (!message.Descriptor.IsExtensionsInitialized(message)) { return false; } @@ -183,7 +183,7 @@ namespace Google.Protobuf } else if (f.FieldType == FieldType.Message || f.FieldType == FieldType.Group) { - if (f.Accessor.HasValue(message)) + if ((f.Accessor as IFieldPresenceAccessor).HasValue(message)) { return ((IMessage)f.Accessor.GetValue(message)).IsInitialized(); } @@ -194,7 +194,7 @@ namespace Google.Protobuf } else if (f.IsRequired) { - return f.Accessor.HasValue(message); + return (f.Accessor as IFieldPresenceAccessor).HasValue(message); } else { diff --git a/csharp/src/Google.Protobuf/Reflection/FieldDescriptor.cs b/csharp/src/Google.Protobuf/Reflection/FieldDescriptor.cs index 807e6ce0d..a8b9e69f4 100644 --- a/csharp/src/Google.Protobuf/Reflection/FieldDescriptor.cs +++ b/csharp/src/Google.Protobuf/Reflection/FieldDescriptor.cs @@ -201,7 +201,20 @@ namespace Google.Protobuf.Reflection /// /// Returns true if this field is a packed, repeated field; false otherwise. /// - public bool IsPacked => File.Proto.Syntax != "proto3" ? Proto.Options?.Packed ?? false : !Proto.Options.HasPacked || Proto.Options.Packed; + public bool IsPacked + { + get + { + if (File.Syntax != Syntax.Proto3) + { + return Proto.Options?.Packed ?? false; + } + else + { + return !Proto.Options.HasPacked || Proto.Options.Packed; + } + } + } /// /// Returns true if this field extends another message type; false otherwise. diff --git a/csharp/src/Google.Protobuf/Reflection/FileDescriptor.cs b/csharp/src/Google.Protobuf/Reflection/FileDescriptor.cs index 13266f725..993f428ca 100644 --- a/csharp/src/Google.Protobuf/Reflection/FileDescriptor.cs +++ b/csharp/src/Google.Protobuf/Reflection/FileDescriptor.cs @@ -42,6 +42,25 @@ using static Google.Protobuf.Reflection.SourceCodeInfo.Types; namespace Google.Protobuf.Reflection { + /// + /// The syntax of a .proto file + /// + public enum Syntax + { + /// + /// Proto2 syntax + /// + Proto2, + /// + /// Proto3 syntax + /// + Proto3, + /// + /// An unknown declared syntax + /// + Unknown + } + /// /// Describes a .proto file, including everything defined within. /// IDescriptor is implemented such that the File property returns this descriptor, @@ -87,6 +106,19 @@ namespace Google.Protobuf.Reflection Extensions = new ExtensionCollection(this, generatedCodeInfo?.Extensions); declarations = new Lazy>(CreateDeclarationMap, LazyThreadSafetyMode.ExecutionAndPublication); + + if (!proto.HasSyntax || proto.Syntax == "proto2") + { + Syntax = Syntax.Proto2; + } + else if (proto.Syntax == "proto3") + { + Syntax = Syntax.Proto3; + } + else + { + Syntax = Syntax.Unknown; + } } private Dictionary CreateDeclarationMap() @@ -217,6 +249,11 @@ namespace Google.Protobuf.Reflection /// internal FileDescriptorProto Proto { get; } + /// + /// The syntax of the file + /// + public Syntax Syntax { get; } + /// /// The file name. /// diff --git a/csharp/src/Google.Protobuf/Reflection/IFieldAccessor.cs b/csharp/src/Google.Protobuf/Reflection/IFieldAccessor.cs index 8f7ef3c67..373984e9b 100644 --- a/csharp/src/Google.Protobuf/Reflection/IFieldAccessor.cs +++ b/csharp/src/Google.Protobuf/Reflection/IFieldAccessor.cs @@ -51,11 +51,6 @@ namespace Google.Protobuf.Reflection /// void Clear(IMessage message); - /// - /// Indicates whether the field in the specified message is set. For proto3 fields, this throws an - /// - bool HasValue(IMessage message); - /// /// Fetches the field value. For repeated values, this will be an /// implementation. For map values, this will be an @@ -73,4 +68,15 @@ namespace Google.Protobuf.Reflection /// The field is not a "simple" field. void SetValue(IMessage message, object value); } + + /// + /// Allows field presence to be checked reflectively. This is implemented for all single field accessors + /// + public interface IFieldPresenceAccessor : IFieldAccessor + { + /// + /// Indicates whether the field in the specified message is set. For proto3 fields, this throws an + /// + bool HasValue(IMessage message); + } } \ No newline at end of file diff --git a/csharp/src/Google.Protobuf/Reflection/MessageDescriptor.cs b/csharp/src/Google.Protobuf/Reflection/MessageDescriptor.cs index a6ecfb984..6a93a8faa 100644 --- a/csharp/src/Google.Protobuf/Reflection/MessageDescriptor.cs +++ b/csharp/src/Google.Protobuf/Reflection/MessageDescriptor.cs @@ -136,7 +136,7 @@ namespace Google.Protobuf.Reflection internal DescriptorProto Proto { get; } - internal bool GetIsExtensionsInitialized(IMessage message) + internal bool IsExtensionsInitialized(IMessage message) { if (Proto.ExtensionRange.Count == 0) { diff --git a/csharp/src/Google.Protobuf/Reflection/ReflectionUtil.cs b/csharp/src/Google.Protobuf/Reflection/ReflectionUtil.cs index 5e66b6f2e..28f724b2d 100644 --- a/csharp/src/Google.Protobuf/Reflection/ReflectionUtil.cs +++ b/csharp/src/Google.Protobuf/Reflection/ReflectionUtil.cs @@ -71,7 +71,7 @@ namespace Google.Protobuf.Reflection /// /// Empty Type[] used when calling GetProperty to force property instead of indexer fetching. - /// getFieldFunc + /// internal static readonly Type[] EmptyTypes = new Type[0]; /// diff --git a/csharp/src/Google.Protobuf/Reflection/SingleFieldAccessor.cs b/csharp/src/Google.Protobuf/Reflection/SingleFieldAccessor.cs index dc6e0ca9a..d5a8e6ff7 100644 --- a/csharp/src/Google.Protobuf/Reflection/SingleFieldAccessor.cs +++ b/csharp/src/Google.Protobuf/Reflection/SingleFieldAccessor.cs @@ -39,7 +39,7 @@ namespace Google.Protobuf.Reflection /// /// Accessor for single fields. /// - internal sealed class SingleFieldAccessor : FieldAccessorBase + internal sealed class SingleFieldAccessor : FieldAccessorBase, IFieldPresenceAccessor { // All the work here is actually done in the constructor - it creates the appropriate delegates. // There are various cases to consider, based on the property type (message, string/bytes, or "genuine" primitive) @@ -57,7 +57,7 @@ namespace Google.Protobuf.Reflection throw new ArgumentException("Not all required properties/methods available"); } setValueDelegate = ReflectionUtil.CreateActionIMessageObject(property.GetSetMethod()); - if (descriptor.File.Proto.Syntax == "proto3") + if (descriptor.File.Syntax == Syntax.Proto3) { hasDelegate = message => { throw new InvalidOperationException("HasValue is not implemented for proto3 fields");