Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

get end values from lambda expressions method parameters

Tags:

c#

.net

lambda

basically I want to get the values of the parameters of a called method like this:

var x = 1;
var a = 2;
var b = 3;
Do<HomeController>(o => o.Save(x, "Jimmy", a+b+5, Math.Sqrt(81)));

public static void Do<T>(Expression<Action<T>> expression) where T : Controller
{
  // get the values 1,Jimmy,10,9 here
}
like image 617
Omu Avatar asked Sep 22 '10 06:09

Omu


4 Answers

Well, you'd need to drill into the expression, find the MethodCallExpression, and then look at the arguments to it. Note that we don't have the value of o, so we've got to assume that the arguments to the method don't rely on that. Also we're still assuming that the lambda expression just relies on it being a MethodCallExpression?

EDIT: Okay, here's an edited version which evaluates the arguments. However, it assumes you're not really using the lambda expression parameter within the arguments (which is what the new object[1] is about - it's providing a null parameter, effectively).

using System;
using System.Linq.Expressions;

class Foo
{
    public void Save(int x, string y, int z, double d)
    {
    }
}

class Program
{
    static void Main()
    {
        var x = 1;
        var a = 2;
        var b = 3;
        ShowValues<Foo>(o => o.Save(x, "Jimmy", a + b + 5, Math.Sqrt(81)));
    }

    static void ShowValues<T>(Expression<Action<T>> expression)
    {
        var call = expression.Body as MethodCallExpression;
        if (call == null)
        {
            throw new ArgumentException("Not a method call");
        }
        foreach (Expression argument in call.Arguments)
        {
            LambdaExpression lambda = Expression.Lambda(argument, 
                                                        expression.Parameters);
            Delegate d = lambda.Compile();
            object value = d.DynamicInvoke(new object[1]);
            Console.WriteLine("Got value: {0}", value);
        }
    }
}
like image 74
Jon Skeet Avatar answered Nov 20 '22 10:11

Jon Skeet


As Jon said you can check to see if the expression is a MethodCallExpression

class Program
{
    static void Main(string[] args)
    {
        Program.Do<Controller>(c => c.Save(1, "Jimmy"));
    }

    public static void Do<T>(Expression<Action<T>> expression) where T : Controller
    {
        var body = expression.Body as MethodCallExpression;
        if (body != null)
        {
            foreach (var argument in body.Arguments)
            {
                var constant = argument as ConstantExpression;
                if (constant != null)
                {
                    Console.WriteLine(constant.Value);
                }
            }
        }
    }
}

public class Controller
{
    public void Save(int id, string name)
    {
    }
}
like image 22
Rohan West Avatar answered Nov 20 '22 12:11

Rohan West


My universal answer is below. I hope it will help you and somebody else.

var dict = new Dictionary<string, object>();
var parameterExpressions = methodCallExpr.Arguments;
foreach (var param in method.GetParameters())
{
    var parameterExpression = parameterExpressions[counter];
    var paramValueAccessor = Expression.Lambda(parameterExpression);
    var paramValue = paramValueAccessor.Compile().DynamicInvoke();
    dict[param.Name] = paramValue;
}
like image 3
Pavel Nazarov Avatar answered Nov 20 '22 10:11

Pavel Nazarov


Here is some code that is designed to work with any expression — in the sense that it doesn’t fundamentally assume that you are passing in a method-call expression. However, it is not complete. You will have to fill in the rest.

public static IEnumerable<object> ExtractConstants<T>(
        Expression<Action<T>> expression)
{
    return extractConstants(expression);
}
private static IEnumerable<object> extractConstants(Expression expression)
{
    if (expression == null)
        yield break;

    if (expression is ConstantExpression)
        yield return ((ConstantExpression) expression).Value;

    else if (expression is LambdaExpression)
        foreach (var constant in extractConstants(
                ((LambdaExpression) expression).Body))
            yield return constant;

    else if (expression is UnaryExpression)
        foreach (var constant in extractConstants(
                ((UnaryExpression) expression).Operand))
            yield return constant;

    else if (expression is MethodCallExpression)
    {
        foreach (var arg in ((MethodCallExpression) expression).Arguments)
            foreach (var constant in extractConstants(arg))
                yield return constant;
        foreach (var constant in extractConstants(
                ((MethodCallExpression) expression).Object))
            yield return constant;
    }

    else
        throw new NotImplementedException();
}

For the case that you have mentioned, this already works:

// Prints:
// Jimmy (System.String)
// 1 (System.Int32)
foreach (var constant in Ext.ExtractConstants<string>(
        str => Console.WriteLine("Jimmy", 1)))
    Console.WriteLine("{0} ({1})", constant.ToString(),
                                   constant.GetType().FullName);

For more complex lambda expressions that employ other types of expression nodes, you will have to incrementally extend the above code. Every time you use it and it throws a NotImplementedException, here is what I do:

  • Open the Watch window in the debugger
  • Look at the expression variable and its type
  • Add the necessary code to handle that expression type

Over time the method will become more and more complete.

like image 2
Timwi Avatar answered Nov 20 '22 11:11

Timwi