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

pull/582/head
Jon Skeet 10 years ago
parent 5b9288e47d
commit ef3464dff6
  1. 20
      csharp/src/ProtocolBuffers.Test/GeneratedMessageTest.cs
  2. 29
      csharp/src/ProtocolBuffers/FieldAccess/FieldAccessorTable.cs
  3. 57
      csharp/src/ProtocolBuffers/FieldAccess/OneofAccessor.cs
  4. 15
      csharp/src/ProtocolBuffers/FieldAccess/ReflectionUtil.cs
  5. 1
      src/google/protobuf/compiler/csharp/csharp_message.cc

@ -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);
}
} }
} }

@ -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];
}
}
} }
} }

@ -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<object, int> caseDelegate;
private readonly Func<TMessage, object> caseDelegate; private readonly Action<object> clearDelegate;
private readonly Func<TBuilder, IBuilder> clearDelegate; private OneofDescriptor descriptor;
private MessageDescriptor descriptor;
internal OneofAccessor(MessageDescriptor descriptor, string name) internal OneofAccessor(Type type, string propertyName, OneofDescriptor descriptor)
{ {
this.descriptor = descriptor; PropertyInfo property = type.GetProperty(propertyName + "Case");
MethodInfo clearMethod = typeof(TBuilder).GetMethod("Clear" + name); if (property == null || !property.CanRead)
PropertyInfo caseProperty = typeof(TMessage).GetProperty(name + "Case");
if (clearMethod == null || caseProperty == null)
{ {
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); this.descriptor = descriptor;
caseDelegate = ReflectionUtil.CreateUpcastDelegate<TMessage>(caseProperty.GetGetMethod()); MethodInfo clearMethod = type.GetMethod("Clear" + propertyName);
clearDelegate = ReflectionUtil.CreateActionObject(clearMethod);
} }
/// <summary> public OneofDescriptor Descriptor { get { return descriptor; } }
/// Indicates whether the specified message has set any field in the oneof.
/// </summary>
public bool Has(TMessage message)
{
return ((int) caseDelegate(message) != 0);
}
/// <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;
}*/ }
} }
} }

@ -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.

@ -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",

Loading…
Cancel
Save