ソースを参照

Oneof reflection support. (Generated code changes in next commit.)

Jon Skeet 10 年 前
コミット
ef3464dff6

+ 20 - 0
csharp/src/ProtocolBuffers.Test/GeneratedMessageTest.cs

@@ -734,5 +734,25 @@ namespace Google.Protobuf
             var message = SampleMessages.CreateFullTestAllTypes();
             var message = SampleMessages.CreateFullTestAllTypes();
             Assert.Throws<InvalidCastException>(() => message.Fields[TestAllTypes.SingleBoolFieldNumber].GetValue(new TestMap()));
             Assert.Throws<InvalidCastException>(() => message.Fields[TestAllTypes.SingleBoolFieldNumber].GetValue(new TestMap()));
         }
         }
+
+        [Test]
+        public void Reflection_Oneof()
+        {
+            var message = new TestAllTypes();
+            var fields = message.Fields;
+            Assert.AreEqual(1, fields.Oneofs.Count);
+            var oneof = fields.Oneofs[0];
+            Assert.AreEqual("oneof_field", oneof.Descriptor.Name);
+            Assert.IsNull(oneof.GetCaseFieldDescriptor(message));
+
+            message.OneofString = "foo";
+            Assert.AreSame(fields[TestAllTypes.OneofStringFieldNumber].Descriptor, oneof.GetCaseFieldDescriptor(message));
+
+            message.OneofUint32 = 10;
+            Assert.AreSame(fields[TestAllTypes.OneofUint32FieldNumber].Descriptor, oneof.GetCaseFieldDescriptor(message));
+
+            oneof.Clear(message);
+            Assert.AreEqual(TestAllTypes.OneofFieldOneofCase.None, message.OneofFieldCase);
+        }
     }
     }
 }
 }

+ 26 - 3
csharp/src/ProtocolBuffers/FieldAccess/FieldAccessorTable.cs

@@ -42,6 +42,7 @@ namespace Google.Protobuf.FieldAccess
     public sealed class FieldAccessorTable
     public sealed class FieldAccessorTable
     {
     {
         private readonly ReadOnlyCollection<IFieldAccessor> accessors;
         private readonly ReadOnlyCollection<IFieldAccessor> accessors;
+        private readonly ReadOnlyCollection<OneofAccessor> oneofs;
         private readonly MessageDescriptor descriptor;
         private readonly MessageDescriptor descriptor;
 
 
         /// <summary>
         /// <summary>
@@ -51,7 +52,7 @@ namespace Google.Protobuf.FieldAccess
         /// <param name="type">The CLR type for the message.</param>
         /// <param name="type">The CLR type for the message.</param>
         /// <param name="descriptor">The type's descriptor</param>
         /// <param name="descriptor">The type's descriptor</param>
         /// <param name="propertyNames">The Pascal-case names of all the field-based properties in the message.</param>
         /// <param name="propertyNames">The Pascal-case names of all the field-based properties in the message.</param>
-        public FieldAccessorTable(Type type, MessageDescriptor descriptor, string[] propertyNames)
+        public FieldAccessorTable(Type type, MessageDescriptor descriptor, string[] propertyNames, string[] oneofPropertyNames)
         {
         {
             this.descriptor = descriptor;
             this.descriptor = descriptor;
             var accessorsArray = new IFieldAccessor[descriptor.Fields.Count];
             var accessorsArray = new IFieldAccessor[descriptor.Fields.Count];
@@ -65,7 +66,13 @@ namespace Google.Protobuf.FieldAccess
                     : (IFieldAccessor) new SingleFieldAccessor(type, name, field);
                     : (IFieldAccessor) new SingleFieldAccessor(type, name, field);
             }
             }
             accessors = new ReadOnlyCollection<IFieldAccessor>(accessorsArray);
             accessors = new ReadOnlyCollection<IFieldAccessor>(accessorsArray);
-            // TODO(jonskeet): Oneof support
+            var oneofsArray = new OneofAccessor[descriptor.Oneofs.Count];
+            for (int i = 0; i < oneofsArray.Length; i++)
+            {
+                var oneof = descriptor.Oneofs[i];
+                oneofsArray[i] = new OneofAccessor(type, oneofPropertyNames[i], oneof);
+            }
+            oneofs = new ReadOnlyCollection<OneofAccessor>(oneofsArray);
         }
         }
 
 
         // TODO: Validate the name here... should possibly make this type a more "general reflection access" type,
         // TODO: Validate the name here... should possibly make this type a more "general reflection access" type,
@@ -75,6 +82,10 @@ namespace Google.Protobuf.FieldAccess
         /// </summary>
         /// </summary>
         public ReadOnlyCollection<IFieldAccessor> Accessors { get { return accessors; } }
         public ReadOnlyCollection<IFieldAccessor> Accessors { get { return accessors; } }
 
 
+        public ReadOnlyCollection<OneofAccessor> Oneofs { get { return oneofs; } }
+
+        // TODO: Review the API for the indexers. Now that we have fields and oneofs, it's not as clear...
+
         public IFieldAccessor this[int fieldNumber]
         public IFieldAccessor this[int fieldNumber]
         {
         {
             get
             get
@@ -84,7 +95,7 @@ namespace Google.Protobuf.FieldAccess
             }
             }
         }
         }
 
 
-        internal IFieldAccessor this[FieldDescriptor field]
+        public IFieldAccessor this[FieldDescriptor field]
         {
         {
             get
             get
             {
             {
@@ -95,5 +106,17 @@ namespace Google.Protobuf.FieldAccess
                 return accessors[field.Index];
                 return accessors[field.Index];
             }
             }
         }
         }
+
+        public OneofAccessor this[OneofDescriptor oneof]
+        {
+            get
+            {
+                if (oneof.ContainingType != descriptor)
+                {
+                    throw new ArgumentException("OneofDescriptor does not match message type.");
+                }
+                return oneofs[oneof.Index];
+            }
+        }
     }
     }
 }
 }

+ 26 - 31
csharp/src/ProtocolBuffers/FieldAccess/OneofAccessor.cs

@@ -30,62 +30,57 @@
 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #endregion
 #endregion
 
 
+using Google.Protobuf.Descriptors;
+using System;
+using System.Reflection;
+
 namespace Google.Protobuf.FieldAccess
 namespace Google.Protobuf.FieldAccess
 {
 {
-    // TODO(jonskeet): Add "new" oneof API support
-
     /// <summary>
     /// <summary>
-    /// Access for an oneof
+    /// Reflection access for a oneof, allowing clear and "get case" actions.
     /// </summary>
     /// </summary>
-    internal class OneofAccessor<TMessage> where TMessage : IMessage<TMessage>
+    public sealed class OneofAccessor
     {
     {
-        /*
-        private readonly Func<TMessage, object> caseDelegate;
-        private readonly Func<TBuilder, IBuilder> clearDelegate;
-        private MessageDescriptor descriptor;
+        private readonly Func<object, int> caseDelegate;
+        private readonly Action<object> clearDelegate;
+        private OneofDescriptor descriptor;
 
 
-        internal OneofAccessor(MessageDescriptor descriptor, string name) 
+        internal OneofAccessor(Type type, string propertyName, OneofDescriptor descriptor) 
         {
         {
-            this.descriptor = descriptor;
-            MethodInfo clearMethod = typeof(TBuilder).GetMethod("Clear" + name);
-            PropertyInfo caseProperty = typeof(TMessage).GetProperty(name + "Case");
-            if (clearMethod == null || caseProperty == null)
+            PropertyInfo property = type.GetProperty(propertyName + "Case");
+            if (property == null || !property.CanRead)
             {
             {
-                throw new ArgumentException("Not all required properties/methods available for oneof");
+                throw new ArgumentException("Not all required properties/methods available");
             }
             }
-            
+            this.descriptor = descriptor;
+            caseDelegate = ReflectionUtil.CreateFuncObjectT<int>(property.GetGetMethod());
 
 
-            clearDelegate = ReflectionUtil.CreateDelegateFunc<TBuilder, IBuilder>(clearMethod);
-            caseDelegate = ReflectionUtil.CreateUpcastDelegate<TMessage>(caseProperty.GetGetMethod());
+            this.descriptor = descriptor;
+            MethodInfo clearMethod = type.GetMethod("Clear" + propertyName);
+            clearDelegate = ReflectionUtil.CreateActionObject(clearMethod);
         }
         }
 
 
-        /// <summary>
-        /// Indicates whether the specified message has set any field in the oneof.
-        /// </summary>
-        public bool Has(TMessage message)
-        {
-            return ((int) caseDelegate(message) != 0);
-        }
+        public OneofDescriptor Descriptor { get { return descriptor; } }
 
 
         /// <summary>
         /// <summary>
-        /// Clears the oneof in the specified builder.
+        /// Clears the oneof in the specified message.
         /// </summary>
         /// </summary>
-        public void Clear(TBuilder builder)
+        public void Clear(object message)
         {
         {
-            clearDelegate(builder);
+            clearDelegate(message);
         }
         }
 
 
         /// <summary>
         /// <summary>
         /// Indicates which field in the oneof is set for specified message
         /// Indicates which field in the oneof is set for specified message
         /// </summary>
         /// </summary>
-        public virtual FieldDescriptor GetOneofFieldDescriptor(TMessage message)
+        public FieldDescriptor GetCaseFieldDescriptor(object message)
         {
         {
-            int fieldNumber = (int) caseDelegate(message);
+            int fieldNumber = caseDelegate(message);
             if (fieldNumber > 0)
             if (fieldNumber > 0)
             {
             {
-                return descriptor.FindFieldByNumber(fieldNumber);
+                return descriptor.ContainingType.FindFieldByNumber(fieldNumber);
             }
             }
             return null;
             return null;
-        }*/
+        }
     }
     }
 }
 }

+ 14 - 1
csharp/src/ProtocolBuffers/FieldAccess/ReflectionUtil.cs

@@ -63,7 +63,20 @@ namespace Google.Protobuf.FieldAccess
             Expression upcast = Expression.Convert(call, typeof(object));
             Expression upcast = Expression.Convert(call, typeof(object));
             return Expression.Lambda<Func<object, object>>(upcast, parameter).Compile();
             return Expression.Lambda<Func<object, object>>(upcast, parameter).Compile();
         }
         }
-        
+
+        /// <summary>
+        /// Creates a delegate which will cast the argument to the appropriate method target type,
+        /// call the method on it, then convert the result to the specified type.
+        /// </summary>
+        internal static Func<object, T> CreateFuncObjectT<T>(MethodInfo method)
+        {
+            ParameterExpression parameter = Expression.Parameter(typeof(object), "p");
+            Expression downcast = Expression.Convert(parameter, method.DeclaringType);
+            Expression call = Expression.Call(downcast, method);
+            Expression upcast = Expression.Convert(call, typeof(T));
+            return Expression.Lambda<Func<object, T>>(upcast, parameter).Compile();
+        }
+
         /// <summary>
         /// <summary>
         /// Creates a delegate which will execute the given method after casting the first argument to
         /// Creates a delegate which will execute the given method after casting the first argument to
         /// the target type of the method, and the second argument to the first parameter type of the method.
         /// the target type of the method, and the second argument to the first parameter type of the method.

+ 1 - 0
src/google/protobuf/compiler/csharp/csharp_message.cc

@@ -151,6 +151,7 @@ void MessageGenerator::GenerateStaticVariableInitializers(io::Printer* printer)
     printer->Print("\"$property_name$\", ",
     printer->Print("\"$property_name$\", ",
                    "property_name", GetPropertyName(descriptor_->field(i)));
                    "property_name", GetPropertyName(descriptor_->field(i)));
   }
   }
+  printer->Print("}, new string[] { ");
   for (int i = 0; i < descriptor_->oneof_decl_count(); i++) {
   for (int i = 0; i < descriptor_->oneof_decl_count(); i++) {
     printer->Print("\"$oneof_name$\", ",
     printer->Print("\"$oneof_name$\", ",
                    "oneof_name",
                    "oneof_name",