3

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

caldis
  • 226
  • 1
  • 9
  • Are the number of parameters variable, or will it always be 3? Furthermore, if you do not know the types of the parameters at compile time, how would you plan to call these functions? – Moeri Dec 10 '13 at 10:15
  • @Moeri No. The number of variables is variable. But that is not a problem. I can switch over the number of variables and cast accordingly. But I cannot cast to a dynamic type. – caldis Dec 10 '13 at 10:16
  • 1
    I would recommend you dynamically build a casted Expression invocation. See this question for more details: http://stackoverflow.com/questions/2169062/faster-way-to-cast-a-funct-t2-to-funct-object. Then you can always call it with Func – Moeri Dec 10 '13 at 10:20
  • @Moeri The dynamic casting of the input and return parameter achieved the goal. – caldis Dec 10 '13 at 16:37
  • Glad to hear that. Might I suggest you answer your own question then, for future SO visitors? :-) – Moeri Dec 10 '13 at 17:05
  • Will do. Once back at work. – caldis Dec 10 '13 at 22:37

2 Answers2

1

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

caldis
  • 226
  • 1
  • 9
0

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);
MichaelD
  • 8,377
  • 10
  • 42
  • 47
  • This does not do what I am looking for since exp doesn't have a type of Func. It is an delegate and could be cast to Func. But I do not now T1, T2 and T3 at compile time. – caldis Dec 10 '13 at 10:20
  • If you don't know T1, T2 an T3 at compile time, then you shouldn't be using generics for this. It's the purpose of generics , using typesafe referencing where you know the type @ compile time – MichaelD Dec 10 '13 at 10:45