Search code examples
c#genericslambdareflectiondynamic-typing

How to create a lambda expression with dynamic types


My programme will get service name and method name during its run time, and to execute the method dynamically I m creating a lambda expression based func.

public static Func<object,object,object> CreateLambdaExpression2(string tService, string methodName)
{
    var inputServiceType = Type.GetType(tService);
    var methodInfo = inputServiceType.GetMethod(methodName);
    var inputType = methodInfo.GetParameters().First().ParameterType;
    var outputType = methodInfo.ReturnParameter.ParameterType;

    var instance = Expression.Parameter(inputServiceType, "serviceInstance");
    var input = Expression.Parameter(inputType, "inputData");
    var call = Expression.Call(instance, methodInfo, input);

    var lambdaFunc = Expression.Lambda<Func<object,object, object>>(call, instance, input).Compile(); //<= this line throws the error.
    return lambdaFunc;
}

but it won't and it will throw error at run time

var compiledMethod = ServiceMapper.CreateLambdaExpression2(tService,"Get");

var serviceInstance = new TestDemoService();
var inputData = new TestDemoPersonRequest()
{
    Id = 555
};
var result = compiledMethod(serviceInstance, inputData);

System.ArgumentException: 'ParameterExpression of type 'UnitTests.ITestDemoService' cannot be used for delegate parameter of type 'System.Object''

Is there a way to specify the type for the Expression.Lambda?

Expression.Lambda<Func<object,object, object>>

to

Expression.Lambda<Func<inputServiceType ,inputType , outputType >>

Solution

  • Your expression lacks a type casts. To make it compile you need explicitly convert object to inputServiceType and so on. Try this code:

    var objType = typeof(object);
    var instance = Expression.Parameter(objType, "serviceInstance");
    var input = Expression.Parameter(objType, "inputData");
    var call = Expression.Call(
        Expression.Convert(instance, inputServiceType), // convert first arg 
        methodInfo,
        Expression.Convert(input, inputType)); // and second
    var body = Expression.Convert(call, objType); // and even return type
    
    var lambdaFunc = Expression.Lambda<Func<object, object, object>>(body, instance, input).Compile();
    return lambdaFunc;
    

    Try it out here


    EDIT You can make it more type safe:

    public static Func<TService, TInput, TReturn>
        CreateTypedLambdaExpression<TService, TInput, TReturn>(
            string methodName)
    {
        var inputServiceType = typeof(TService);
        var methodInfo = inputServiceType.GetMethod(methodName);
        var inputType = typeof(TInput);
    
        // now you need to check if TInput is equal to methodInfo.GetParameters().First().ParameterType
        // same check for return type
    
        var instance = Expression.Parameter(inputServiceType, "serviceInstance");
        var input = Expression.Parameter(inputType, "inputData");
        var call = Expression.Call(instance, methodInfo, input);
    
        var lambdaFunc = Expression.Lambda<Func<TService, TInput, TReturn>>(call, instance, input);
        return lambdaFunc.Compile();
    }
    

    Usage:

    var func = CreateTypedLambdaExpression<Program, bool, int>("TestMethod");
    var result = func(service, false);