1 #region Copyright notice and license 2 // Protocol Buffers - Google's data interchange format 3 // Copyright 2015 Google Inc. All rights reserved. 4 // 5 // Use of this source code is governed by a BSD-style 6 // license that can be found in the LICENSE file or at 7 // https://developers.google.com/open-source/licenses/bsd 8 #endregion 9 10 using System; 11 using System.Diagnostics.CodeAnalysis; 12 using System.Reflection; 13 using Google.Protobuf.Compatibility; 14 15 namespace Google.Protobuf.Reflection 16 { 17 /// <summary> 18 /// Accessor for single fields. 19 /// </summary> 20 internal sealed class SingleFieldAccessor : FieldAccessorBase 21 { 22 // All the work here is actually done in the constructor - it creates the appropriate delegates. 23 // There are various cases to consider, based on the property type (message, string/bytes, or "genuine" primitive) 24 // and proto2 vs proto3 for non-message types, as proto3 doesn't support "full" presence detection or default 25 // values. 26 27 private readonly Action<IMessage, object> setValueDelegate; 28 private readonly Action<IMessage> clearDelegate; 29 private readonly Func<IMessage, bool> hasDelegate; 30 SingleFieldAccessor( [DynamicallyAccessedMembers(GeneratedClrTypeInfo.MessageAccessibility)] Type messageType, PropertyInfo property, FieldDescriptor descriptor)31 internal SingleFieldAccessor( 32 [DynamicallyAccessedMembers(GeneratedClrTypeInfo.MessageAccessibility)] 33 Type messageType, PropertyInfo property, FieldDescriptor descriptor) : base(property, descriptor) 34 { 35 if (!property.CanWrite) 36 { 37 throw new ArgumentException("Not all required properties/methods available"); 38 } 39 setValueDelegate = ReflectionUtil.CreateActionIMessageObject(property.GetSetMethod()); 40 41 // Note: this looks worrying in that we access the containing oneof, which isn't valid until cross-linking 42 // is complete... but field accessors aren't created until after cross-linking. 43 // The oneof itself won't be cross-linked yet, but that's okay: the oneof accessor is created 44 // earlier. 45 46 // Message fields always support presence, via null checks. 47 if (descriptor.FieldType == FieldType.Message) 48 { 49 hasDelegate = message => GetValue(message) != null; 50 clearDelegate = message => SetValue(message, null); 51 } 52 // Oneof fields always support presence, via case checks. 53 // Note that clearing the field is a no-op unless that specific field is the current "case". 54 else if (descriptor.RealContainingOneof != null) 55 { 56 var oneofAccessor = descriptor.RealContainingOneof.Accessor; 57 hasDelegate = message => oneofAccessor.GetCaseFieldDescriptor(message) == descriptor; 58 clearDelegate = message => 59 { 60 // Clear on a field only affects the oneof itself if the current case is the field we're accessing. 61 if (oneofAccessor.GetCaseFieldDescriptor(message) == descriptor) 62 { 63 oneofAccessor.Clear(message); 64 } 65 }; 66 } 67 // Anything else that supports presence should have a "HasXyz" property and a "ClearXyz" 68 // method. 69 else if (descriptor.HasPresence) 70 { 71 MethodInfo hasMethod = messageType.GetRuntimeProperty("Has" + property.Name).GetMethod; 72 if (hasMethod == null) 73 { 74 throw new ArgumentException("Not all required properties/methods are available"); 75 } 76 hasDelegate = ReflectionUtil.CreateFuncIMessageBool(hasMethod); 77 MethodInfo clearMethod = messageType.GetRuntimeMethod("Clear" + property.Name, ReflectionUtil.EmptyTypes); 78 if (clearMethod == null) 79 { 80 throw new ArgumentException("Not all required properties/methods are available"); 81 } 82 clearDelegate = ReflectionUtil.CreateActionIMessage(clearMethod); 83 } 84 // Otherwise, we don't support presence. 85 else 86 { 87 hasDelegate = message => throw new InvalidOperationException("Presence is not implemented for this field"); 88 89 // While presence isn't supported, clearing still is; it's just setting to a default value. 90 object defaultValue = GetDefaultValue(descriptor); 91 clearDelegate = message => SetValue(message, defaultValue); 92 } 93 } 94 95 private static object GetDefaultValue(FieldDescriptor descriptor) => 96 descriptor.FieldType switch 97 { 98 FieldType.Bool => false, 99 FieldType.Bytes => ByteString.Empty, 100 FieldType.String => "", 101 FieldType.Double => 0.0, 102 FieldType.SInt32 or FieldType.Int32 or FieldType.SFixed32 or FieldType.Enum => 0, 103 FieldType.Fixed32 or FieldType.UInt32 => (uint)0, 104 FieldType.Fixed64 or FieldType.UInt64 => 0UL, 105 FieldType.SFixed64 or FieldType.Int64 or FieldType.SInt64 => 0L, 106 FieldType.Float => 0f, 107 FieldType.Message or FieldType.Group => null, 108 _ => throw new ArgumentException("Invalid field type"), 109 }; 110 111 public override void Clear(IMessage message) => clearDelegate(message); 112 public override bool HasValue(IMessage message) => hasDelegate(message); SetValue(IMessage message, object value)113 public override void SetValue(IMessage message, object value) => setValueDelegate(message, value); 114 } 115 } 116