Getting object array containing the values of para

2019-08-05 04:03发布

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.

标签: c# lambda
3条回答
爷、活的狠高调
2楼-- · 2019-08-05 04:17

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.

查看更多
乱世女痞
3楼-- · 2019-08-05 04:22

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");
    }
}
查看更多
祖国的老花朵
4楼-- · 2019-08-05 04:34

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.)

查看更多
登录 后发表回答