chyyran
chyyran

Reputation: 2636

Invoking a MethodCallExpression with named parameter constants

Say I have some function like so

public string TestValue(string hello, Guid world)
{
    return hello + world;
}

Assuming that objectParams is a dictionary of object Dictionary<string, object> that map the parameter name to the value, I'm currently matching the parameter values to the method name like so:

var method = this.GetType().GetMethod("TestValue");
var methodParameters = method.GetParameters();
var paramMatcher = (from paramValue in objectParams
                    from methodParam in methodParameters
                    where param.Key == clrParam.Name
                    select (name: clrParam.Name,
                            type: clrParam.ParameterType,
                            value: paramValue.Value));

I then build the expression to call the method TestValue like so, but this is the part I'm having trouble with.

var paramExpress = (from param in paramMatcher
                    select Expression.Assign(Expression.Parameter(param.type, param.name), Expression.Constant(param.value)));

Func<object> result = Expression.Lambda<Func<object>(Expression.Call(Expression.Constant(this),
    method, paramExpress)).Compile();
var res = result.Invoke(); //should return 'somestringxxxxxxx-xxxx...etc'

The issue is that I can't guarantee that the parameter values are in call order so I want to rely on the name of the parameters to call. I'm not sure on how to assign the constant values to their parameter expressions properly. Running this code results in the exception System.InvalidOperationException: 'variable 'hello' of type 'System.String' referenced from scope '', but it is not defined' when compiling the lambda.

Upvotes: 0

Views: 674

Answers (1)

Nikita
Nikita

Reputation: 900

Expression.Assign is a binary operation, so the variable in the left part takes a new value calculated in the right part of expression.

On these lines:

var paramExpress = (from param in paramMatcher
     select Expression.Assign(Expression.Parameter(param.type, param.name),
                Expression.Constant(param.value, param.type)));

Func<object> result = Expression.Lambda<Func<object>(Expression.Call(Expression.Constant(this),
    method, paramExpress)).Compile();

you've received and just didn't used actual parameters values, presented in the right parts of binary expressions.

Solution:

public class C
{
    public string TestValue(string hello, Guid world)
    {
        return hello + world;
    }

    public string Execute()
    {
        var objectParams = new Dictionary<string, object>()
        {
            {"hello", "somestring"},
            {"world", Guid.NewGuid()}
        };
        var method = this.GetType().GetMethod("TestValue");
        var methodParameters = method.GetParameters();
        var paramMatcher = (from paramValue in objectParams
            from methodParam in methodParameters
            where paramValue.Key == methodParam.Name
            orderby methodParam.Position  // <-- preserves original order
            select (name: methodParam.Name,
            type: methodParam.ParameterType,
            value: paramValue.Value));

        var paramExpress = (from param in paramMatcher
             select Expression.Assign(Expression.Parameter(param.type, param.name),
                    Expression.Constant(param.value, param.type)));

        var values = paramExpress.Select(v => v.Right); // !!!

        Func<string> result = Expression.Lambda<Func<string>>(Expression.Call(Expression.Constant(this),
            method, values)).Compile();
        return result.Invoke(); // returns "somestringxxxxxxx-xxxx..."
    }
}

Upvotes: 1

Related Questions