Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.)

like image 309
caldis Avatar asked Apr 17 '26 02:04

caldis


1 Answers

-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.

like image 70
caldis Avatar answered Apr 18 '26 15:04

caldis



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!