|
|
|
@ -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,130 @@ 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 target type of the method, and the second argument to the first parameter type of the method. |
|
|
|
|
/// the type that declares the method, and the second argument to the first parameter type of the method. |
|
|
|
|
/// </summary> |
|
|
|
|
internal static Action<IMessage, object> CreateActionIMessageObject(MethodInfo method) |
|
|
|
|
{ |
|
|
|
|
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(); |
|
|
|
|
} |
|
|
|
|
/// <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. |
|
|
|
|
/// type that declares the method. |
|
|
|
|
/// </summary> |
|
|
|
|
internal static Action<IMessage> CreateActionIMessage(MethodInfo method) |
|
|
|
|
/// <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> |
|
|
|
|
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 |
|
|
|
|
{ |
|
|
|
|
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 |
|
|
|
|
{ |
|
|
|
|
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(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
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 |
|
|
|
|
{ |
|
|
|
|
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; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|