diff options
Diffstat (limited to 'csharp/src/ProtocolBuffers/FieldAccess/SingleFieldAccessor.cs')
-rw-r--r-- | csharp/src/ProtocolBuffers/FieldAccess/SingleFieldAccessor.cs | 89 |
1 files changed, 89 insertions, 0 deletions
diff --git a/csharp/src/ProtocolBuffers/FieldAccess/SingleFieldAccessor.cs b/csharp/src/ProtocolBuffers/FieldAccess/SingleFieldAccessor.cs new file mode 100644 index 00000000..a352d3a2 --- /dev/null +++ b/csharp/src/ProtocolBuffers/FieldAccess/SingleFieldAccessor.cs @@ -0,0 +1,89 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using Google.Protobuf; +using Google.Protobuf.Descriptors; +using Google.Protobuf.FieldAccess; + +namespace Google.Protobuf.FieldAccess +{ + /// <summary> + /// Accessor for single fields. + /// </summary> + /// <typeparam name="T">The type of message containing the field.</typeparam> + internal sealed class SingleFieldAccessor<T> : FieldAccessorBase<T> where T : IMessage<T> + { + // 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) + // and proto2 vs proto3 for non-message types, as proto3 doesn't support "full" presence detection or default + // values. + + private readonly Action<T, object> setValueDelegate; + private readonly Action<T> clearDelegate; + private readonly Func<T, bool> hasValueDelegate; + + internal SingleFieldAccessor(FieldDescriptor descriptor, string name, bool supportsFieldPresence) : base(name) + { + PropertyInfo property = typeof(T).GetProperty(name); + // We know there *is* such a property, or the base class constructor would have thrown. We should be able to write + // to it though. + if (!property.CanWrite) + { + throw new ArgumentException("Not all required properties/methods available"); + } + setValueDelegate = ReflectionUtil.CreateDowncastDelegate<T>(property.GetSetMethod()); + + var clrType = property.PropertyType; + + if (typeof(IMessage).IsAssignableFrom(clrType)) + { + // Message types are simple - we only need to detect nullity. + clearDelegate = message => SetValue(message, null); + hasValueDelegate = message => GetValue(message) == null; + } + + if (supportsFieldPresence) + { + // Proto2: we expect a HasFoo property and a ClearFoo method. + // For strings and byte arrays, setting the property to null would have the equivalent effect, + // but we generate the method for consistency, which makes this simpler. + PropertyInfo hasProperty = typeof(T).GetProperty("Has" + name); + MethodInfo clearMethod = typeof(T).GetMethod("Clear" + name); + if (hasProperty == null || clearMethod == null || !hasProperty.CanRead) + { + throw new ArgumentException("Not all required properties/methods available"); + } + hasValueDelegate = ReflectionUtil.CreateDelegateFunc<T, bool>(hasProperty.GetGetMethod()); + clearDelegate = ReflectionUtil.CreateDelegateAction<T>(clearMethod); + } + else + { + /* + // TODO(jonskeet): Reimplement. We need a better way of working out default values. + // Proto3: for field detection, we just need the default value of the field (0, "", byte[0] etc) + // To clear a field, we set the value to that default. + object defaultValue = descriptor.DefaultValue; + hasValueDelegate = message => GetValue(message).Equals(defaultValue); + clearDelegate = message => SetValue(message, defaultValue); + */ + } + } + + public override bool HasValue(T message) + { + return hasValueDelegate(message); + } + + public override void Clear(T message) + { + clearDelegate(message); + } + + public override void SetValue(T message, object value) + { + setValueDelegate(message, value); + } + } +} |