Searching for a more elegant way to turn a MethodInfo to a Func through Expressions

I have the following code:

       public static Func<object[], object> CreateStaticFunc(this MethodInfo methodInfo)
    {
        var types = methodInfo.GetParameters().Select(p => p.ParameterType);



        if (methodInfo.ReturnType.Equals((typeof(void))))
        {
            Debug.LogError("Cannot create a Func from a method without a return type!");
            return null;
        }

        if (!methodInfo.IsStatic)
        {
            Debug.LogError("Cannot create a static Func from a non-static function!");
            return null;
        }

        var funcParams = methodInfo.GetParameters();

        var input = Expression.Parameter(typeof(object[]), "input");

        Func<object[], object> returned = null;

        //TODO research how to make this cleaner.
        switch (funcParams.Length)
        {
            case 0:
                returned = Expression.Lambda<Func<object[], object>>(Expression.Call(methodInfo), input).Compile();
                break;
            case 1:
                returned = Expression.Lambda<Func<object[], object>>(Expression.Call(methodInfo, 
                    Expression.Convert(Expression.ArrayAccess(input, Expression.Constant(0)), funcParams[0].ParameterType)), input).Compile();
                break;
            case 2:
                returned = Expression.Lambda<Func<object[], object>>(Expression.Call(methodInfo,
                    Expression.Convert(Expression.ArrayAccess(input, Expression.Constant(0)), funcParams[0].ParameterType),
                    Expression.Convert(Expression.ArrayAccess(input, Expression.Constant(1)), funcParams[1].ParameterType)), input).Compile();
                break;
            case 3:
                returned = Expression.Lambda<Func<object[], object>>(Expression.Call(methodInfo,
                    Expression.Convert(Expression.ArrayAccess(input, Expression.Constant(0)), funcParams[0].ParameterType),
                    Expression.Convert(Expression.ArrayAccess(input, Expression.Constant(1)), funcParams[1].ParameterType),
                    Expression.Convert(Expression.ArrayAccess(input, Expression.Constant(2)), funcParams[2].ParameterType)), input).Compile();
                break;
            case 4:
                returned = Expression.Lambda<Func<object[], object>>(Expression.Call(methodInfo,
                    Expression.Convert(Expression.ArrayAccess(input, Expression.Constant(0)), funcParams[0].ParameterType),
                    Expression.Convert(Expression.ArrayAccess(input, Expression.Constant(1)), funcParams[1].ParameterType),
                    Expression.Convert(Expression.ArrayAccess(input, Expression.Constant(2)), funcParams[2].ParameterType),
                    Expression.Convert(Expression.ArrayAccess(input, Expression.Constant(3)), funcParams[3].ParameterType)), input).Compile();
                break;
            case 5:
                returned = Expression.Lambda<Func<object[], object>>(Expression.Call(methodInfo,
                   Expression.Convert(Expression.ArrayAccess(input, Expression.Constant(0)), funcParams[0].ParameterType),
                    Expression.Convert(Expression.ArrayAccess(input, Expression.Constant(1)), funcParams[1].ParameterType),
                    Expression.Convert(Expression.ArrayAccess(input, Expression.Constant(2)), funcParams[2].ParameterType),
                    Expression.Convert(Expression.ArrayAccess(input, Expression.Constant(3)), funcParams[3].ParameterType),
                    Expression.Convert(Expression.ArrayAccess(input, Expression.Constant(4)), funcParams[4].ParameterType)), input).Compile();
                break;
            case 6:
                returned = Expression.Lambda<Func<object[], object>>(Expression.Call(methodInfo,
                   Expression.Convert(Expression.ArrayAccess(input, Expression.Constant(0)), funcParams[0].ParameterType),
                    Expression.Convert(Expression.ArrayAccess(input, Expression.Constant(1)), funcParams[1].ParameterType),
                    Expression.Convert(Expression.ArrayAccess(input, Expression.Constant(2)), funcParams[2].ParameterType),
                    Expression.Convert(Expression.ArrayAccess(input, Expression.Constant(3)), funcParams[3].ParameterType),
                    Expression.Convert(Expression.ArrayAccess(input, Expression.Constant(4)), funcParams[4].ParameterType),
                    Expression.Convert(Expression.ArrayAccess(input, Expression.Constant(5)), funcParams[5].ParameterType)), input).Compile();
                break;
        }
       

        return returned;
    }

}

This turns the MethodInfo into a Func/delegate so that it may be cached to improve performance since MethodInfo.Invoke is not very performant.

The code seems to work, but I cannot write a more elegant/concise way of doing this since I do not know the number of parameters ahead of time.

Can anyone help with this? Is it even possible?

1 answer

  • answered 2020-11-24 16:22 Diego Rafael Souza

    I guess you can just use the Expression.Lambda's method overloads to get it done, even if your method has no parameters.

    Like this:

    public static Func<object[], object> CreateStaticFunc(this MethodInfo methodInfo)
    {
        var types = methodInfo.GetParameters().Select(p => p.ParameterType);
    
        if (methodInfo.ReturnType.Equals((typeof(void))))
        {
            Console.WriteLine("Cannot create a Func from a method without a return type!");
            return null;
        }
    
        if (!methodInfo.IsStatic)
        {
            Console.WriteLine("Cannot create a static Func from a non-static function!");
            return null;
        }
    
        var input = Expression.Parameter(typeof(object[]), "input");
    
        Func<object[], object> returned = GetParamatersExpressions(methodInfo, input);
    
        return returned;
    }
    
    private static Func<object[], object> GetParamatersExpressions(MethodInfo methodInfo, ParameterExpression input)
    {
        Func<object[], object> result;
        var funcParams = methodInfo.GetParameters();
    
        IList<UnaryExpression> parms = new List<UnaryExpression>();
        for (int i = 0; i < funcParams.Length; i++)
            parms.Add(Expression.Convert(Expression.ArrayAccess(input, Expression.Constant(i)), funcParams[i].ParameterType));
    
        result = Expression.Lambda<Func<object[], object>>(Expression.Call(methodInfo, parms), input).Compile();
        return result;
    }
    

    Hope it helps