tags:

views:

96

answers:

3

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
}
+2  A: 

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);
        }
    }
}
Jon Skeet
@Omu: But do you really want code which would break - at execution time only - if you used *any* other form of lambda expression? If you're *always* going to be calling Save, why not just pass the parameters in directly?
Jon Skeet
@Jon Skeet, I'm going to be calling lots of other methods with different parameters, I didn't knew that I can call only with constant values, I guess I'm going to have to investigate this further
Omu
@Omu: This is the problem with just dumping code into a question with no explanation. I did *say* it was brittle, and would fail as soon as you started using any other pattern...
Jon Skeet
@Jon Skeet I just try to make the questions as simple as possible
Omu
@Jon Skeet, I've edited my question, please take a look
Omu
@Omu: I've edited my answer to match the question. I think it does what you want, although I'd still say it's pretty brittle.
Jon Skeet
@Jon Skeet: thank you very much, that's what I wanted
Omu
+3  A: 

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)
    {
    }
}
Rohan West
@Rohan: I love the fact that our demo code is almost identical :)
Jon Skeet
@Jon, I Didn't have time to do any error checking tho :(
Rohan West
@Rohan: Yeah, it's pretty brittle either way. I doubt it's the best approach, but we don't have much context.
Jon Skeet
@Rohan West I've edited my question, could you please take a look
Omu
A: 

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.

Timwi