From 8e23d4e49c69385537869ed1332d52317c904d59 Mon Sep 17 00:00:00 2001 From: Jon Skeet Date: Thu, 16 Nov 2017 18:20:01 +0000 Subject: [PATCH] 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. --- .../Reflection/ReflectionUtil.cs | 56 +++++++++++++++++-- 1 file changed, 51 insertions(+), 5 deletions(-) diff --git a/csharp/src/Google.Protobuf/Reflection/ReflectionUtil.cs b/csharp/src/Google.Protobuf/Reflection/ReflectionUtil.cs index c2771d723f..3a5b3bd9df 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 /// /// 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. /// internal static Func CreateFuncIMessageInt32(MethodInfo method) => - GetReflectionHelper(method.DeclaringType, typeof(object)).CreateFuncIMessageInt32(method); + GetReflectionHelper(method.DeclaringType, method.ReturnType).CreateFuncIMessageInt32(method); /// /// 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. /// internal static Action CreateActionIMessage(MethodInfo method) => - GetReflectionHelper(method.DeclaringType, typeof(object)).CreateActionIMessage(method); + GetReflectionHelper(method.DeclaringType, typeof(object)).CreateActionIMessage(method); /// /// 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 : IReflectionHelper { + public Func CreateFuncIMessageInt32(MethodInfo method) { - var del = (Func) method.CreateDelegate(typeof(Func)); - return message => del((T1) message); + // On pleasant runtimes, we can create a Func from a method returning + // an enum based on an int. That's the fast path. + if (CanConvertEnumFuncToInt32Func) + { + var del = (Func) method.CreateDelegate(typeof(Func)); + 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) method.CreateDelegate(typeof(Func)); + return message => (int) (object) del((T1) message); + } } public Action 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.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)); + 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