diff options
Diffstat (limited to 'csharp/src/Google.Protobuf/Reflection/ReflectionUtil.cs')
-rw-r--r-- | csharp/src/Google.Protobuf/Reflection/ReflectionUtil.cs | 173 |
1 files changed, 135 insertions, 38 deletions
diff --git a/csharp/src/Google.Protobuf/Reflection/ReflectionUtil.cs b/csharp/src/Google.Protobuf/Reflection/ReflectionUtil.cs index df820ca3..80d5c774 100644 --- a/csharp/src/Google.Protobuf/Reflection/ReflectionUtil.cs +++ b/csharp/src/Google.Protobuf/Reflection/ReflectionUtil.cs @@ -30,11 +30,14 @@ // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #endregion +using Google.Protobuf.Compatibility; using System; -using System.Collections.Generic; -using System.Linq.Expressions; using System.Reflection; +#if NET35 +using Google.Protobuf.Compatibility; +#endif + namespace Google.Protobuf.Reflection { /// <summary> @@ -53,55 +56,149 @@ namespace Google.Protobuf.Reflection internal static readonly Type[] EmptyTypes = new Type[0]; /// <summary> - /// Creates a delegate which will cast the argument to the appropriate method target type, + /// Creates a delegate which will cast the argument to the type that declares the method, /// call the method on it, then convert the result to object. /// </summary> - internal static Func<IMessage, object> CreateFuncIMessageObject(MethodInfo method) - { - ParameterExpression parameter = Expression.Parameter(typeof(IMessage), "p"); - Expression downcast = Expression.Convert(parameter, method.DeclaringType); - Expression call = Expression.Call(downcast, method); - Expression upcast = Expression.Convert(call, typeof(object)); - return Expression.Lambda<Func<IMessage, object>>(upcast, parameter).Compile(); - } + /// <param name="method">The method to create a delegate for, which must be declared in an IMessage + /// implementation.</param> + internal static Func<IMessage, object> CreateFuncIMessageObject(MethodInfo method) => + GetReflectionHelper(method.DeclaringType, method.ReturnType).CreateFuncIMessageObject(method); /// <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. + /// Creates a delegate which will cast the argument to the type that declares the method, + /// 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, T> CreateFuncIMessageT<T>(MethodInfo method) - { - ParameterExpression parameter = Expression.Parameter(typeof(IMessage), "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<IMessage, T>>(upcast, parameter).Compile(); - } + /// <param name="method">The method to create a delegate for, which must be declared in an IMessage + /// implementation.</param> + internal static Func<IMessage, int> CreateFuncIMessageInt32(MethodInfo method) => + GetReflectionHelper(method.DeclaringType, method.ReturnType).CreateFuncIMessageInt32(method); + + /// <summary> + /// Creates a delegate which will execute the given method after casting the first argument to + /// the type that declares the method, and the second argument to the first parameter type of the method. + /// </summary> + /// <param name="method">The method to create a delegate for, which must be declared in an IMessage + /// implementation.</param> + internal static Action<IMessage, object> CreateActionIMessageObject(MethodInfo method) => + GetReflectionHelper(method.DeclaringType, method.GetParameters()[0].ParameterType).CreateActionIMessageObject(method); /// <summary> /// 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. + /// type that declares the method. + /// </summary> + /// <param name="method">The method to create a delegate for, which must be declared in an IMessage + /// implementation.</param> + internal static Action<IMessage> CreateActionIMessage(MethodInfo method) => + GetReflectionHelper(method.DeclaringType, typeof(object)).CreateActionIMessage(method); + + /// <summary> + /// Creates a reflection helper for the given type arguments. Currently these are created on demand + /// rather than cached; this will be "busy" when initially loading a message's descriptor, but after that + /// they can be garbage collected. We could cache them by type if that proves to be important, but creating + /// an object is pretty cheap. /// </summary> - internal static Action<IMessage, object> CreateActionIMessageObject(MethodInfo method) + private static IReflectionHelper GetReflectionHelper(Type t1, Type t2) => + (IReflectionHelper) Activator.CreateInstance(typeof(ReflectionHelper<,>).MakeGenericType(t1, t2)); + + // Non-generic interface allowing us to use an instance of ReflectionHelper<T1, T2> without statically + // knowing the types involved. + private interface IReflectionHelper { - ParameterExpression targetParameter = Expression.Parameter(typeof(IMessage), "target"); - ParameterExpression argParameter = Expression.Parameter(typeof(object), "arg"); - Expression castTarget = Expression.Convert(targetParameter, method.DeclaringType); - Expression castArgument = Expression.Convert(argParameter, method.GetParameters()[0].ParameterType); - Expression call = Expression.Call(castTarget, method, castArgument); - return Expression.Lambda<Action<IMessage, object>>(call, targetParameter, argParameter).Compile(); + Func<IMessage, int> CreateFuncIMessageInt32(MethodInfo method); + Action<IMessage> CreateActionIMessage(MethodInfo method); + Func<IMessage, object> CreateFuncIMessageObject(MethodInfo method); + Action<IMessage, object> CreateActionIMessageObject(MethodInfo method); + } + + private class ReflectionHelper<T1, T2> : IReflectionHelper + { + + public Func<IMessage, int> CreateFuncIMessageInt32(MethodInfo method) + { + // 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) + { + var del = (Action<T1>) method.CreateDelegate(typeof(Action<T1>)); + return message => del((T1) message); + } + + public Func<IMessage, object> CreateFuncIMessageObject(MethodInfo method) + { + var del = (Func<T1, T2>) method.CreateDelegate(typeof(Func<T1, T2>)); + return message => del((T1) message); + } + + public Action<IMessage, object> CreateActionIMessageObject(MethodInfo method) + { + var del = (Action<T1, T2>) method.CreateDelegate(typeof(Action<T1, T2>)); + 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 + { + PreventLinkerFailures(); + // Try to do the conversion using reflection, so we can see whether it's supported. + 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; + } } /// <summary> - /// Creates a delegate which will execute the given method after casting the first argument to - /// the target type of the method. + /// This method is effectively pointless, but only called once. It's present (and called) + /// to avoid the Unity linker from removing code that's only called via reflection. /// </summary> - internal static Action<IMessage> CreateActionIMessage(MethodInfo method) + private static void PreventLinkerFailures() { - ParameterExpression targetParameter = Expression.Parameter(typeof(IMessage), "target"); - Expression castTarget = Expression.Convert(targetParameter, method.DeclaringType); - Expression call = Expression.Call(castTarget, method); - return Expression.Lambda<Action<IMessage>>(call, targetParameter).Compile(); - } + // Exercise the method directly. This should avoid any pro-active linkers from stripping + // the method out. + SampleEnum x = SampleEnumMethod(); + if (x != SampleEnum.X) + { + throw new InvalidOperationException("Failure in reflection utilities"); + } + // Make sure the ReflectionHelper parameterless constructor isn't removed... + var helper = new ReflectionHelper<int, int>(); + } + + public enum SampleEnum + { + X + } + + // Public to make the reflection simpler. + public static SampleEnum SampleEnumMethod() => SampleEnum.X; } -}
\ No newline at end of file +} |