caldis
caldis

Reputation: 226

Create expression function from MethodInfo with unknown signature

I am looking to create a cachable lambda expression just from a methodinfo object retrieved via Type.GetMethod() without coding a typed cast of the function.

I have gotten everthing to work except for the cast from an compiled expression to an typed invokable function.

var parameters = Array.ConvertAll(method.GetParameters(), input => Expression.Parameter(input.ParameterType));
var instanceExp = Expression.Constant(_implementation);
var call = Expression.Call(instanceExp, method, parameters);
var exp = Expression.Lambda(call, parameters).Compile();

What is missing is:

Func<T1,T2,T3> castExp =  (Func<T1,T2,T3>)exp;

What I would like to do is cast to a function with a specific number of parameters without specifying the specic type:

Func<object,object,object> castExp =  (Func<object,object,object>)exp;

This way I could call exp(o1, o2, o3) without ever coding the types of o1 etc. But there is a runtime error casting a function of type Func to Func.

How can I cast the function to some form of func<,,> that allows for passing parameters of unspecified type?

(Btw.: It is not an option to change the signature of the methods which are to be called.)

Upvotes: 3

Views: 2231

Answers (2)

caldis
caldis

Reputation: 226

-1I have partially solved the issue I had.

What I was able to do is create expressions for methods of unknown signature if the method does not use in/out/ref parameters. In that case I had to fall back to the MethodInfo.Invoke call.

private object CallMethod(MethodInfo method, object obj, object[] parameters) {
    // Methods with void as return must be cast to Action instead of Function
    var voidMethod = voidMethod = method.ReturnType == typeof(void);
    // Methods with ref parameters can be called but the parameters won't work.
    var refMethod = Array.FindAll(method.GetParameters(), info => info.ParameterType.IsByRef;
    var paramExprs = getParamExpr(method);
    var paramTypes = getParamTypes(method, paramExprs);
    var instanceExp = Expression.Convert(paramExprs[0], method.DeclaringType);
    Expression call = null;
    if (voidMethod) {
        call = Expression.Call(instanceExp, method, paramTypes);
    } else {
        call = Expression.Convert(Expression.Call(instanceExp, method, paramTypes), typeof(object));
    }
    exp = Expression.Lambda(call, paramExprs).Compile();
    if (voidMethod) {
        switch (method.GetParameters().Length) {
        case 0:
            ((Action<object>)exp)(_obj);
            break;
        case 1:
            ((Action<object, object>)exp)(_obj, parameters[0]);
            break;
        // Continue here with more case statements.
        }
    } else {
        switch (method.GetParameters().Length) {
        case 0:
            result = ((Func<object, object>)exp)(_obj);
            break;
        case 1:
            result = ((Func<object, object, object>)exp)(_obj, parameters[0]);
            break;
        // Continue here with more case statements
        }
    }
    // Error handling omited
    return result;
}

private List<ParameterExpression> getParamExpr(MethodInfo method) {
    var list = new List<ParameterExpression>();
    list.Add(Expression.Parameter(typeof(object), "obj"));
    list.AddRange(Array.ConvertAll(method.GetParameters(), input => Expression.Parameter(typeof(object))));
    return list;
}

private List<Expression> getParamTypes(MethodInfo method, List<ParameterExpression> inList) {
    var list = new List<Expression>();
    var methParams = method.GetParameters();
    list.AddRange(
        // Skip the first item as this is the object on which the method is called.
        inList.Skip(1).Select(
            input => Expression.Convert(
                input,
                Type.GetType(
                        methParams[inList.IndexOf(input)-1].ParameterType.FullName.Replace("&", string.Empty)))));
    return list;
}

I hope it is complete as I have left out a lot of boilerplate for error handling etc.

The expression object can be cached but have to go through the casting everytime you want to call them.

Upvotes: 1

MichaelD
MichaelD

Reputation: 8777

I'm not 100% sure what you want to do but you could create a function that will return your altered function:

Func<T1, T2, T3> GetAlteredFunction<T1, T2, T3>(Func<T1, T2, T3> func)
{
    //execute your logic and return the result
}

so then you can call

Func<object,object,object> castExp =  GetAlteredFunction(exp);

Upvotes: 0

Related Questions