在不使用 nameof 的情况下获取传递的方法的名称
Get the name of the passed method without using nameof
方法的声明很简单,将方法名作为字符串:
public void DoSomethingWithMethodName(string methodName)
{
// Do something with the method name here.
}
并将其称为:
DoSomethingWithMethodName(nameof(SomeClass.SomeMethod));
我想摆脱 nameof
并调用其他方法:
DoSomethingWithMethod(SomeClass.SomeMethod);
然后就可以像上面的例子一样得到方法名了。 "feels" 可以使用一些 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/
和
然后使用:
[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" }
});
如您所见,我传递了一个 属性 然后 "unwinding" 它以到达源代码。我想做同样的事情,但采用如上所述的更简单的设置。
您可以使用[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
。
样本输出
message: Something happened.
member name: DoProcessing
source file path:
C:\Users\user\AppData\Local\Temp\LINQPad5_osjizlla\query_gzfqkl.cs
source line number: 37
基本上你要做的是将参数声明为 Func
与你想要接受的方法签名相匹配,然后将其包装在 Expression
中,这样编译器会给你一个表达式树而不是实际的委托。然后你可以遍历表达式树找到一个 MethodCallExpression
,你可以从中获取方法名称。 (顺便说一句,除了属性之外,您提供的 link 中的示例代码也可以使用方法调用,就像您想要的一样)
what signature this DoSomethingWithMethod
should have
这取决于您希望作为参数的方法的签名。
如果某些方法看起来像:
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) {}
and what it should actually do
我想这个问题实际上是,如何从表达式树中获取方法名称作为字符串?
有几种不同的方法可以做到这一点,这取决于您希望代码的健壮程度。考虑到上面的方法签名允许传递比单个方法调用复杂得多的委托。例如:
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;
//...
}
}
另一种选择是使用访问者模式,这在您有更复杂的场景时尤其有用,例如当有多个方法名称时您想要检索所有方法名称的列表,或者支持属性 或方法调用等的混合
对于此选项,创建一个 class 继承 ExpressionVisitor
并覆盖基础 class 中的适当方法并将结果存储在某处。这是一个例子:
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];
//...
最后,要调用它,您将无法使用缩短的 "method group" 调用模式 DoSomethingWithMethod
,因为 C# 编译器无法自动将方法组转换为表达式树(但是它可以自动将其转换为常规委托,这是您习惯的表示法)。
所以你不能这样做:
DoSomethingWithMethod(SomeMethod);
相反,它必须看起来像一个 lambda 表达式:
DoSomethingWithMethod(t => SomeMethod(t));
或者如果没有参数:
DoSomethingWithMethod(() => SomeMethod());
方法的声明很简单,将方法名作为字符串:
public void DoSomethingWithMethodName(string methodName)
{
// Do something with the method name here.
}
并将其称为:
DoSomethingWithMethodName(nameof(SomeClass.SomeMethod));
我想摆脱 nameof
并调用其他方法:
DoSomethingWithMethod(SomeClass.SomeMethod);
然后就可以像上面的例子一样得到方法名了。 "feels" 可以使用一些 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/
和
然后使用:
[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" }
});
如您所见,我传递了一个 属性 然后 "unwinding" 它以到达源代码。我想做同样的事情,但采用如上所述的更简单的设置。
您可以使用[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
。
样本输出
message: Something happened.
member name: DoProcessing
source file path: C:\Users\user\AppData\Local\Temp\LINQPad5_osjizlla\query_gzfqkl.cs
source line number: 37
基本上你要做的是将参数声明为 Func
与你想要接受的方法签名相匹配,然后将其包装在 Expression
中,这样编译器会给你一个表达式树而不是实际的委托。然后你可以遍历表达式树找到一个 MethodCallExpression
,你可以从中获取方法名称。 (顺便说一句,除了属性之外,您提供的 link 中的示例代码也可以使用方法调用,就像您想要的一样)
what signature this
DoSomethingWithMethod
should have
这取决于您希望作为参数的方法的签名。 如果某些方法看起来像:
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) {}
and what it should actually do
我想这个问题实际上是,如何从表达式树中获取方法名称作为字符串?
有几种不同的方法可以做到这一点,这取决于您希望代码的健壮程度。考虑到上面的方法签名允许传递比单个方法调用复杂得多的委托。例如:
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;
//...
}
}
另一种选择是使用访问者模式,这在您有更复杂的场景时尤其有用,例如当有多个方法名称时您想要检索所有方法名称的列表,或者支持属性 或方法调用等的混合
对于此选项,创建一个 class 继承 ExpressionVisitor
并覆盖基础 class 中的适当方法并将结果存储在某处。这是一个例子:
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];
//...
最后,要调用它,您将无法使用缩短的 "method group" 调用模式 DoSomethingWithMethod
,因为 C# 编译器无法自动将方法组转换为表达式树(但是它可以自动将其转换为常规委托,这是您习惯的表示法)。
所以你不能这样做:
DoSomethingWithMethod(SomeMethod);
相反,它必须看起来像一个 lambda 表达式:
DoSomethingWithMethod(t => SomeMethod(t));
或者如果没有参数:
DoSomethingWithMethod(() => SomeMethod());