tags:

views:

56

answers:

3

I'm writing a function that will be passed a lambda expression, and I want to convert the parameters that the lambda takes to an object array.

The only way I've been able to do it is with code I borrowed from here, and my function looks something like this:

public class MyClassBase<T> where T : class 
{
    protected void DoStuff(Expression<Action<T>> selector)
    {
        ReadOnlyCollection<Expression> methodArgumentsCollection = (selector.Body as MethodCallExpression).Arguments;
        object[] methodArguments = methodArgumentsCollection.Select(c => Expression.Lambda(c is UnaryExpression ?
                        ((UnaryExpression)c).Operand : c)
                        .Compile()
                        .DynamicInvoke())
                        .ToArray();
        // do more stuff with methodArguments
    }       
}

interface IMyInterface
{
    void MethodSingleParam(string param1);
}

class MyClass : MyClassBase<IMyInterface>
{
    void MakeCall()
    {
        DoStuff(x => x.MethodSingleParam("abc"));
    }
}

Is there a neater way of doing this? It seems like overkill having to compile and invoke the lambda when all I want is the parameter values.

+2  A: 

Well, the most natural thing to do with a lambda, is to run it. Indeed, some of the convenience they offer is from being able to use them in places where we don't need to be as explicit about the parameters used than if we used a method instead.

Obtaining the parameters, isn't the "natural" thing to do, it's peeking into what's going on in the expression. Really, it's not that distant from the sort of thing that's done with reflection.

It's a sign of a good language that the more natural things are the easiest things to do. Obviously, the easier one can make anything else, the better, but this doesn't seem like overkill to me.

Jon Hanna
+1  A: 

Well, in the general case, a Compile() is pretty much all you can do. Imagine if you'd call

DoStuff(x => x.MethodSingleParam(Math.Abs(a.SomeMethod())));

How would you handle that? You'd need to execute Math.Abs(a.SomeMethod()) to find out what value it returns. This also shows you that this type of introspection is rather brittle (no guarantees that a second call to a.SomeMethod() returns the same value).

However, when the parameter passed is a constant (represented by a ConstantExpression), you will be certain, and you don't need a Compile():

protected void DoStuff(Expression<Action<T>> selector)
{
    ReadOnlyCollection<Expression> methodArgumentsCollection = 
                  (selector.Body as MethodCallExpression).Arguments;
    object[] methodArguments = methodArgumentsCollection.Select(c =>
              c is ConstantExpression 
              ? ((ConstantExpression) c).Value 
              : ... ).ToArray();
    // do more stuff with methodArguments
}

The check for ConstantExpression above ensures that the following code will not call Compile():

DoStuff(x => x.MethodSingleParam("abc"));

Like I said, compiling is not really a safe thing to do here, so you might as well return null or throw an error in such cases. (Which is why I've placed a ... here; you can put your Compile back here if you need to.)

Ruben
Thanks for your answer. In retrospect I realised that using a lambda is this situation isn't really appropriate, but your answer greatly helped improve my understanding.
John Sibly
+1  A: 

Could your code look more like this?

public class MyClassBase<T>
{
    protected void DoStuff(params T[] arguments)
    {
        // do more stuff with arguments
    }
}

class MyClass : MyClassBase<string>
{
    void MakeCall()
    {
        DoStuff("abc");
    }
}
Douglas
That's what we tried initially. What we're actually doing with this is serialise a bunch of function calls to XML, so we also need to know which method is calling DoStuff. The only solution we could think of for that was to use something like:StackFrame frame = new StackTrace().GetFrame(1);MethodBase method = frame.GetMethod();Using the above with params T[] seemed much easier to get wrong-and we'd find out at runtime rather than compile time.
John Sibly
Passing in the lambda you can simply do:MethodCallExpression methodCallExpression = selector.Body as MethodCallExpression;MethodInfo methodInfo = methodCallExpression.Method;
John Sibly