在不使用 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

https://msdn.microsoft.com/en-us/library/system.runtime.compilerservices.callermembernameattribute(v=vs.110).aspx

基本上你要做的是将参数声明为 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());