简体   繁体   中英

(Action<T>).Name does not return expected values

I have the following method (used to generate friendly error messages in unit tests):

protected string MethodName<TTestedType>(Action<TTestedType> call)
{
    return string.Format("{0}.{1}", typeof(TTestedType).FullName, call.Method.Name);
}

But when I call it as follows, I don't get the expected results:

var nm = MethodName<MyController>(ctrl => ctrl.Create());

After running this code, nm contains "<Create_CreateShowsView>b__8" , and not (as expected) "Create" . How should I change the code to obtain the expected result?

You need to pass an Expression instead of an Action . It's actually not that hard to use expression trees for this once you understand what the tree looks like.

The line of code:

(MyClass c) => c.TestMethod();

Can be broken down as as a lambda expression (the entire block), containing one parameter ( c , on the left side), and a body ( c.TestMethod() , on the right side).

Then the "body" is a method call on a specific object (which is the parameter, c ), an actual method ( TestMethod ), and a set of arguments (in this case, there aren't any).

Visually:

            LambdaExpression   [ (MyClass c) => c.TestMethod() ]
             /           \
            /             \
           /               \
      Parameters          Body   [ MethodCallExpression: c.TestMethod() ]
          |              /    \
          |             /      \
    1: MyClass c    Object [c]  \
                                /\
                               /  \
                              /    \
             Method [TestMethod]  Arguments [Empty]

What you want is the method name, inside the method call expression, inside the body of the lambda expression. So the code to get this is:

static string GetInnerMethodName<T>(Expression<Action<T>> expr)
{
    MethodCallExpression mce = expr.Body as MethodCallExpression;
    return (mce != null) ? mce.Method.Name : null;
}

Of course, this will only work if the Expression<Action<T>> passed in is a genuine method call expression; the consumer of this method could technically pass in any expression, in which case this will just return null . You can adjust this to throw an exception instead, return a default value, or perform whatever other action you think is appropriate.

You don't need to do anything special to use this - it's the same usage as your original method:

string methodName = GetInnerMethodName<MyClass>(c => c.TestMethod());

That b__8 thing is the name of the method that is generated by the C# compiler for your lambda. You can see this by using Reflector , for instance.

If you need it to say "Create", you'll have to actually make a method named Create . And of course it needs to fit into an Action , so it will have to return void .


In simple cases, you could create a method that takes a string indicating a method to be called, and return an action that calls that method on an object of a specified type. See for instance this tutorial .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM