在不使用nameof的情况下获取传递的方法的名称

时间:2018-06-25 23:29:03

标签: c# lambda expression func

声明方法很容易,该方法将方法名称作为字符串:

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" }
    });

如您所见,我正在传递一个属性,然后“展开”它以获取源代码。我想做同样的事情,但是如上所述,设置更简单。

2 个答案:

答案 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

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

答案 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());