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.
精彩评论