声明方法很容易,该方法将方法名称作为字符串:
public void DoSomethingWithMethodName(string methodName)
{
// Do something with the method name here.
}
并将其称为:
DoSomethingWithMethodName(nameof(SomeClass.SomeMethod));
我想摆脱nameof
并调用其他方法:
DoSomethingWithMethod(SomeClass.SomeMethod);
,然后能够获得与上面示例相同的方法名称。使用某些Expression
和/或Func
魔术可以“感觉”到这样做。问题是此DoSomethingWithMethod
应该具有什么签名以及它实际上应该做什么!
===================================
这个问题似乎引起很多混乱,答案假定我没有问。这暗示了我的目标,但不能正确。这是针对某些不同的问题(对此我有一个解决方案)。我可以声明:
private async Task CheckDictionary(Expression<Func<LookupDictionary>> property, int? expectedIndex = null)
{
await RunTest(async wb =>
{
var isFirst = true;
foreach (var variable in property.Compile().Invoke())
{
// Pass the override value for the first index.
await CheckSetLookupIndex(wb, GetPathOfProperty(property), variable, isFirst ? expectedIndex : null);
isFirst = false;
}
});
}
GetPathOfProperty
来自:
https://www.automatetheplanet.com/get-property-names-using-lambda-expressions/
和Fully-qualified property name
然后使用:
[Fact]
public async Task CommercialExcelRaterService_ShouldNotThrowOnNumberOfStories() =>
await CheckDictionary(() => EqNumberOfStories, 2);
其中EqNumberOfStories
是:
public static LookupDictionary EqNumberOfStories { get; } = new LookupDictionary(new Dictionary<int, string>
{
{ 1, "" },
{ 2, "1 to 8" },
{ 3, "9 to 20" },
{ 4, "Over 20" }
});
如您所见,我正在传递一个属性,然后“展开”它以获取源代码。我想做同样的事情,但是如上所述,设置更简单。
答案 0 :(得分:2)
您可以使用[CallerMemberName]
来获取调用方法的名称。
public void DoProcessing()
{
TraceMessage("Something happened.");
}
public void TraceMessage(string message,
[System.Runtime.CompilerServices.CallerMemberName] string memberName = "",
[System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "",
[System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0)
{
System.Diagnostics.Trace.WriteLine("message: " + message);
System.Diagnostics.Trace.WriteLine("member name: " + memberName);
System.Diagnostics.Trace.WriteLine("source file path: " + sourceFilePath);
System.Diagnostics.Trace.WriteLine("source line number: " + sourceLineNumber);
}
在上面的示例中,将为memberName
参数分配值DoProcessing
。
样本输出
消息:发生了什么事。
成员名称:DoProcessing
源文件路径: C:\ Users \ user \ AppData \ Local \ Temp \ LINQPad5_osjizlla \ query_gzfqkl.cs
源行号:37
答案 1 :(得分:1)
基本上,您要做的是将参数声明为Func
,该参数与要接受的方法签名相匹配,然后将其包装在Expression
中,以便编译器为您提供一个表达式树而不是实际的代表。然后,您可以遍历表达式树以找到MethodCallExpression
,从中可以获取方法名称。 (顺便说一句,您提供的链接中的示例代码除了属性之外,也可以与方法调用一起使用)
此
DoSomethingWithMethod
应该具有什么签名
这取决于您要用作参数的方法的签名。 如果某种方法如下:
public MyReturnType SomeMethod(MyParameterType parameter) {}
然后DoSomethingWithMethod
签名如下:
public void DoSomethingWithMethod(Expression<Func<MyParameterType,MyReturnType>> methodExpression) {}
如果您要接受签名稍有不同的方法,也可以将其设为通用(但是,如果要接受具有不同数量参数的方法,则必须使用重载,而且C#编译器可能不会解析通用)在这种情况下,您可以自动输入参数,而您必须明确指定它们)
public void DoSomethingWithMethod<TParam,TReturn>(Expression<Func<TParam,TReturn>> methodExpression) {}
及其实际操作
我想这个问题实际上是,如何从表达式树中获取方法名称作为字符串?
有两种不同的方法可以做到这一点,这取决于您希望代码的健壮性。考虑到上面的方法签名允许传递一个委托,该委托比单个方法调用复杂得多。例如:
DoSomethingWithMethod(t => t.SomeMethod().SomeOtherMethod(5) + AnotherThing(t));
如果搜索从以上生成的表达式树,您会发现很多方法调用,而不仅仅是一个。如果您只想强制传递的参数是单个方法调用,尝试将表达式Body
属性强制转换为MethodCallExpression
public void DoSomethingWithMethod<TParam,TReturn>(Expression<Func<TParam,TReturn>> methodExpression)
{
if (methodExpression.Body is MethodCallExpression methodCall)
{
var methodName = methodCall.Method.Name;
//...
}
}
另一种选择是使用访问者模式,这特别有用,如果您有一个更复杂的场景,例如您想在有多个(例如支持多个)时检索所有方法名称的列表属性或方法调用等。
对于此选项,创建一个继承ExpressionVisitor
并覆盖基类中适当方法的类,并将结果存储在某个位置。这是一个示例:
class MyVisitor : ExpressionVisitor
{
public List<string> Names { get; } = new List<string>();
protected override Expression VisitMember(MemberExpression node)
{
if(node.Member.MemberType == MemberTypes.Method)
{
Names.Add(node.Member.Name);
}
return base.VisitMember(node);
}
}
您可以这样称呼它:
var visitor = new MyVisitor();
visitor.Visit(methodExpression.Body);
var methodName = visitor.Names[0];
//...
最后,要调用它,您将无法使用缩短的“方法组”模式来调用DoSomethingWithMethod
,因为C#编译器无法自动将方法组转换为表达式树(它可以但是会自动将其转换为常规委托(这是您惯用的表示法)。
所以你不能做
DoSomethingWithMethod(SomeMethod);
相反,它必须看起来像是lambda表达式:
DoSomethingWithMethod(t => SomeMethod(t));
,或者如果没有参数:
DoSomethingWithMethod(() => SomeMethod());