Tapeti/Tapeti/Helpers/ExpressionInvoker.cs

71 lines
2.8 KiB
C#

using System;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
// Note: I also tried a version which accepts a ValueFactory[] to reduce the object array allocations,
// but the performance benefits were negligable and it still allocated more memory than expected.
//
// Reflection.Emit is another option which I've dabbled with, but that's too much of a risk for me to
// attempt at the moment and there's probably other code which could benefit from optimization more.
namespace Tapeti.Helpers
{
/// <summary>
/// The precompiled version of MethodInfo.Invoke.
/// </summary>
/// <param name="target">The instance on which the method should be called.</param>
/// <param name="args">The arguments passed to the method.</param>
public delegate object ExpressionInvoke(object? target, params object?[] args);
/// <summary>
/// Provides a way to create a precompiled version of MethodInfo.Invoke with decreased overhead.
/// </summary>
public static class ExpressionInvokeExtensions
{
/// <summary>
/// Creates a precompiled version of MethodInfo.Invoke with decreased overhead.
/// </summary>
public static ExpressionInvoke CreateExpressionInvoke(this MethodInfo method)
{
if (method.DeclaringType == null)
throw new ArgumentException("Method must have a declaring type");
var argsParameter = Expression.Parameter(typeof(object[]), "args");
var parameters = method.GetParameters().Select(
(p, i) =>
{
var argsIndexExpression = Expression.Constant(i, typeof(int));
var argExpression = Expression.ArrayIndex(argsParameter, argsIndexExpression);
return Expression.Convert(argExpression, p.ParameterType) as Expression;
})
.ToArray();
var instanceParameter = Expression.Parameter(typeof(object), "target");
Expression? instance = method.IsStatic
? null
: Expression.Convert(instanceParameter, method.DeclaringType);
var invoke = Expression.Call(instance, method, parameters);
Expression<ExpressionInvoke> lambda;
if (method.ReturnType != typeof(void))
{
var result = Expression.Convert(invoke, typeof(object));
lambda = Expression.Lambda<ExpressionInvoke>(result, instanceParameter, argsParameter);
}
else
{
var nullResult = Expression.Constant(null, typeof(object));
var body = Expression.Block(invoke, nullResult);
lambda = Expression.Lambda<ExpressionInvoke>(body, instanceParameter, argsParameter);
}
return lambda.Compile();
}
}
}