aboutsummaryrefslogtreecommitdiffhomepage
path: root/csharp
diff options
context:
space:
mode:
authorGravatar Jon Skeet <jonskeet@google.com>2017-11-16 18:20:01 +0000
committerGravatar Jon Skeet <jonskeet@google.com>2018-03-27 08:42:27 +0100
commit8e23d4e49c69385537869ed1332d52317c904d59 (patch)
treeb0fe3db688b79b77482b6b1d655397426c47329e /csharp
parentaa59eaa77b8ede7b0ed120e9bda8a92348ede946 (diff)
Work around an "old runtime" issue with reflection
For oneofs, to get the case, we need to call the property that returns the enum value. We really want it as an int, and modern runtimes allow us to create a delegate which returns an int from the method. (I suspect that the MS runtime has always allowed that.) Old versions of Mono (e.g. used by Unity3d) don't allow that, so we have to convert the enum value to an int via boxing. It's ugly, but it should work.
Diffstat (limited to 'csharp')
-rw-r--r--csharp/src/Google.Protobuf/Reflection/ReflectionUtil.cs56
1 files changed, 51 insertions, 5 deletions
diff --git a/csharp/src/Google.Protobuf/Reflection/ReflectionUtil.cs b/csharp/src/Google.Protobuf/Reflection/ReflectionUtil.cs
index c2771d72..3a5b3bd9 100644
--- a/csharp/src/Google.Protobuf/Reflection/ReflectionUtil.cs
+++ b/csharp/src/Google.Protobuf/Reflection/ReflectionUtil.cs
@@ -30,6 +30,7 @@
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#endregion
+using Google.Protobuf.Compatibility;
using System;
using System.Reflection;
@@ -63,10 +64,12 @@ namespace Google.Protobuf.Reflection
/// <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.
+ /// call the method on it, then convert the result to the specified type. The method is expected
+ /// to actually return an enum (because of where we're calling it - for oneof cases). Sometimes that
+ /// means we need some extra work to perform conversions.
/// </summary>
internal static Func<IMessage, int> CreateFuncIMessageInt32(MethodInfo method) =>
- GetReflectionHelper(method.DeclaringType, typeof(object)).CreateFuncIMessageInt32(method);
+ GetReflectionHelper(method.DeclaringType, method.ReturnType).CreateFuncIMessageInt32(method);
/// <summary>
/// Creates a delegate which will execute the given method after casting the first argument to
@@ -80,7 +83,7 @@ namespace Google.Protobuf.Reflection
/// the target type of the method.
/// </summary>
internal static Action<IMessage> CreateActionIMessage(MethodInfo method) =>
- GetReflectionHelper(method.DeclaringType, typeof(object)).CreateActionIMessage(method);
+ GetReflectionHelper(method.DeclaringType, typeof(object)).CreateActionIMessage(method);
/// <summary>
/// Creates a reflection helper for the given type arguments. Currently these are created on demand
@@ -103,10 +106,24 @@ namespace Google.Protobuf.Reflection
private class ReflectionHelper<T1, T2> : IReflectionHelper
{
+
public Func<IMessage, int> CreateFuncIMessageInt32(MethodInfo method)
{
- var del = (Func<T1, int>) method.CreateDelegate(typeof(Func<T1, int>));
- return message => del((T1) message);
+ // On pleasant runtimes, we can create a Func<int> from a method returning
+ // an enum based on an int. That's the fast path.
+ if (CanConvertEnumFuncToInt32Func)
+ {
+ var del = (Func<T1, int>) method.CreateDelegate(typeof(Func<T1, int>));
+ return message => del((T1) message);
+ }
+ else
+ {
+ // On some runtimes (e.g. old Mono) the return type has to be exactly correct,
+ // so we go via boxing. Reflection is already fairly inefficient, and this is
+ // only used for one-of case checking, fortunately.
+ var del = (Func<T1, T2>) method.CreateDelegate(typeof(Func<T1, T2>));
+ return message => (int) (object) del((T1) message);
+ }
}
public Action<IMessage> CreateActionIMessage(MethodInfo method)
@@ -127,5 +144,34 @@ namespace Google.Protobuf.Reflection
return (message, arg) => del((T1) message, (T2) arg);
}
}
+
+ // Runtime compatibility checking code - see ReflectionHelper<T1, T2>.CreateFuncIMessageInt32 for
+ // details about why we're doing this.
+
+ // Deliberately not inside the generic type. We only want to check this once.
+ private static bool CanConvertEnumFuncToInt32Func { get; } = CheckCanConvertEnumFuncToInt32Func();
+
+ private static bool CheckCanConvertEnumFuncToInt32Func()
+ {
+ try
+ {
+ MethodInfo method = typeof(ReflectionUtil).GetMethod(nameof(SampleEnumMethod));
+ // If this passes, we're in a reasonable runtime.
+ method.CreateDelegate(typeof(Func<int>));
+ return true;
+ }
+ catch (ArgumentException)
+ {
+ return false;
+ }
+ }
+
+ public enum SampleEnum
+ {
+ X
+ }
+
+ // Public to make the reflection simpler.
+ public static SampleEnum SampleEnumMethod() => SampleEnum.X;
}
} \ No newline at end of file