From e38294a62d7f37c0661273a9a26fda16d557423f Mon Sep 17 00:00:00 2001 From: Jon Skeet Date: Tue, 9 Jun 2015 19:30:44 +0100 Subject: First pass at the mutable API. Quite a bit more to do - in particular, it's pretty slow right now. --- .../FieldAccess/SingleFieldAccessor.cs | 89 ++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 csharp/src/ProtocolBuffers/FieldAccess/SingleFieldAccessor.cs (limited to 'csharp/src/ProtocolBuffers/FieldAccess/SingleFieldAccessor.cs') 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 +{ + /// + /// Accessor for single fields. + /// + /// The type of message containing the field. + internal sealed class SingleFieldAccessor : FieldAccessorBase where T : IMessage + { + // 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 setValueDelegate; + private readonly Action clearDelegate; + private readonly Func 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(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(hasProperty.GetGetMethod()); + clearDelegate = ReflectionUtil.CreateDelegateAction(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); + } + } +} -- cgit v1.2.3