代码之家  ›  专栏  ›  技术社区  ›  Konstantin Konstantinov

获取传递的方法的名称,而不使用name of

  •  0
  • Konstantin Konstantinov  · 技术社区  · 8 年前

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

    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 回复  |  直到 8 年前
        1
  •  1
  •   Dave M    8 年前

    基本上,您需要做的是将参数声明为 Func 匹配要接受的方法签名,然后将其包装为 Expression 这样编译器就会给你一个表达式树而不是一个真正的委托。然后你可以在表达式树中找到 MethodCallExpression 从中可以获取方法名。(顺便说一下,您提供的链接中的示例代码也将使用方法调用,就像您想要的一样,除了属性)

    这是什么签名 DoSomethingWithMethod 应该有

    这取决于作为参数的方法的签名。 如果某个方法看起来像:

    public MyReturnType SomeMethod(MyParameterType parameter) {}
    

    然后 剂量法 签名如下:

    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 财产 方法调用表达式

    public void DoSomethingWithMethod<TParam,TReturn>(Expression<Func<TParam,TReturn>> methodExpression)
    {
        if (methodExpression.Body is MethodCallExpression methodCall)
        {
            var methodName = methodCall.Method.Name;
            //...
        }
    }
    

    另一种选择是使用visitor模式,这非常有用,特别是当您有一个更复杂的场景时,例如您希望检索所有方法名的列表,例如有多个方法名,或者支持属性或方法调用的混合等。

    对于此选项,创建一个继承 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];
    //...
    

    最后,要调用它,您将无法使用缩短的“方法组”调用模式 剂量法 因为C编译器不能自动将方法组转换为表达式树(它可以自动将其转换为常规的委托,这是您所使用的符号)。

    所以你不能:

    DoSomethingWithMethod(SomeMethod);
    

    相反,它必须看起来像lambda表达式:

    DoSomethingWithMethod(t => SomeMethod(t));
    

    或者如果没有参数:

    DoSomethingWithMethod(() => SomeMethod());
    
        2
  •  2
  •   Mayank    8 年前

    你可以用 [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 PARAM将被赋值 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