能不能用反射来找到当前正在执行的方法的名字?

Posted

技术标签:

【中文标题】能不能用反射来找到当前正在执行的方法的名字?【英文标题】:Can you use reflection to find the name of the currently executing method? 【发布时间】:2010-09-07 19:59:48 【问题描述】:

正如标题所说:反射能否给你当前正在执行的方法的名称。

由于海森堡问题,我倾向于不猜测。你如何调用一个方法来告诉你当前的方法而不改变当前的方法是什么?但我希望有人能证明我错了。

更新:

第 2 部分:这也可以用来查看属性的内部代码吗? 第 3 部分:性能如何?

最终结果 我了解了 MethodBase.GetCurrentMethod()。我还了解到,我不仅可以创建堆栈跟踪,还可以根据需要只创建我需要的确切帧。

要在属性中使用它,只需使用 .Substring(4) 删除 'set_' 或 'get_'。

【问题讨论】:

乔尔,我知道这是一个老问题,但你所说的创建方法的精确框架是什么意思? 它指的是调用堆栈中的特定项:堆栈跟踪中重要的部分。 对于异步方法,您可以使用来自Using System.Reflection to Get a Method's Full Name的GetMethodContextName() 【参考方案1】:

对于非async 可以使用的方法

System.Reflection.MethodBase.GetCurrentMethod().Name;

https://docs.microsoft.com/en-us/dotnet/api/system.reflection.methodbase.getcurrentmethod

请记住,对于async 方法,它将返回“MoveNext”。

【讨论】:

请注意,这并不总是产生预期的结果。即,小方法或属性通常在发布版本中内联,在这种情况下,结果将是调用者的方法名称。 据我所知,没有。因为在运行时,执行指针不再提供 MSIL(它是 JITted)。如果您知道方法的名称,您仍然可以使用反射。关键是,当内联时,当前执行的方法现在是另一种方法(即,一个或多个更高的堆栈)。换句话说,方法消失了。即使你用 NoInlining 标记你的方法,它仍然有可能被尾调用优化,在这种情况下它也消失了。但是,它会在调试版本中工作。 为了避免内联在方法顶部添加 [MethodImpl(MethodImplOptions.NoInlining)] 属性。 async 方法中,您很可能会得到“MoveNext”作为方法名称。 有没有办法获取异步方法的当前方法名?【参考方案2】:

从 .NET 4.5 开始,您还可以使用 [CallerMemberName]。

示例:属性设置器(回答第 2 部分):

protected void SetProperty<T>(T value, [CallerMemberName] string property = null)

    this.propertyValues[property] = value;
    OnPropertyChanged(property);


public string SomeProperty

    set  SetProperty(value); 

编译器会在调用点提供匹配的字符串字面量,所以基本上没有性能开销。

【讨论】:

这太棒了!我正在使用其他日志记录答案中描述的StackFrame(1) 方法,该方法似乎一直有效,直到 Jitter 决定开始内联事物。出于性能原因,我不想添加属性来防止内联。使用[CallerMemberName] 方法解决了这个问题。谢谢! [CallerMemberName] 可在 net 4 使用BCL build packed 考虑到在调试模式下使用 StackFrame(1) 应该可以工作。但是在编译时使用释放模式时,可能会有一些优化,堆栈可能不是你所期望的。 这不会返回调用成员(即 SomeProperty),而不是当前执行的方法吗? 是的,调用 setter 将导致调用 OnPropertyChanged("SomeProperty") 而不是 OnPropertyChanged("SetProperty")【参考方案3】:

Lex 提供的 sn-p 有点长,所以我指出了重要的部分,因为没有人使用完全相同的技术:

string MethodName = new StackFrame(0).GetMethod().Name;

这应该返回与 MethodBase.GetCurrentMethod().Name 技术相同的结果,但仍然值得指出,因为我可以使用索引 1 在其自己的方法中实现一次上一个 方法并从许多不同的属性中调用它。此外,它只返回一帧而不是整个堆栈跟踪:

private string GetPropertyName()
  //.SubString(4) strips the property prefix (get|set) from the name
    return new StackFrame(1).GetMethod().Name.Substring(4);

这也是单线;)

【讨论】:

可以是辅助类中的公共静态字符串 GetPropertyName() 吗?静态方法? 与 Ed Guiness 的回答相同:发布版本中的堆栈可能不同,在内联或尾调用优化的情况下,第一个方法可能与当前方法不同。 如果您使用的是 .Net 4.5,请参阅 John Nilsson 的回答,了解解决内联问题的好方法。 这可能比接受的答案和上述答案更好【参考方案4】:

在空控制台程序的 Main 方法中试试这个:

MethodBase method = MethodBase.GetCurrentMethod();
Console.WriteLine(method.Name);

控制台输出:Main

【讨论】:

【参考方案5】:

肯定是的。

如果你想要一个对象来操作,我实际上使用了这样的函数:

public static T CreateWrapper<T>(Exception innerException, params object[] parameterValues) where T : Exception, new()

    if (parameterValues == null)
    
        parameterValues = new object[0];
    

    Exception exception   = null;
    StringBuilder builder = new StringBuilder();
    MethodBase method     = new StackFrame(2).GetMethod();
    ParameterInfo[] parameters = method.GetParameters();
    builder.AppendFormat(CultureInfo.InvariantCulture, ExceptionFormat, new object[]  method.DeclaringType.Name, method.Name );
    if ((parameters.Length > 0) || (parameterValues.Length > 0))
    
        builder.Append(GetParameterList(parameters, parameterValues));
    

    exception = (Exception)Activator.CreateInstance(typeof(T), new object[]  builder.ToString(), innerException );
    return (T)exception;

这一行:

MethodBase method     = new StackFrame(2).GetMethod();

遍历堆栈帧以找到调用方法,然后我们使用反射来获取传递给它的参数信息值,以用于通用错误报告函数。要获取当前方法,只需使用当前堆栈帧 (1)。

正如其他人所说的当前方法名称,您也可以使用:

MethodBase.GetCurrentMethod()

我更喜欢遍历堆栈,因为如果从内部查看该方法,它无论如何都会创建一个 StackCrawlMark。直接寻址堆栈对我来说似乎更清楚

在 4.5 之后,您现在可以使用 [CallerMemberNameAttribute] 作为方法参数的一部分来获取方法名称的字符串 - 这在某些情况下可能会有所帮助(但实际上是上面的示例)

public void Foo ([CallerMemberName] string methodName = null)

这似乎主要是 INotifyPropertyChanged 支持的解决方案,以前您的事件代码中到处都是字符串。

【讨论】:

不傻;我只是把它们传进去了。你可能会做一些事情让它看起来更简单,但努力回报率似乎有利于保持简单。本质上,开发人员只是复制方法签名的参数列表(当然是删除类型)。 它是什么:ExceptionFormat和GetParameterList? 回复晚了,但是:ExceptionFormat 是一个常量字符串格式,GetParameterList 是一个简单的函数,它使用值格式化参数(你可以内联)【参考方案6】:

比较获取方法名称的方法——在 LinqPad 中使用arbitrary timing construct:

代码

void Main()

    // from http://blogs.msdn.com/b/webdevelopertips/archive/2009/06/23/tip-83-did-you-know-you-can-get-the-name-of-the-calling-method-from-the-stack-using-reflection.aspx
    // and https://***.com/questions/2652460/c-sharp-how-to-get-the-name-of-the-current-method-from-code

    var fn = new methods();

    fn.reflection().Dump("reflection");
    fn.stacktrace().Dump("stacktrace");
    fn.inlineconstant().Dump("inlineconstant");
    fn.constant().Dump("constant");
    fn.expr().Dump("expr");
    fn.exprmember().Dump("exprmember");
    fn.callermember().Dump("callermember");

    new Perf 
         "reflection", n => fn.reflection() ,
         "stacktrace", n => fn.stacktrace() ,
         "inlineconstant", n => fn.inlineconstant() ,
         "constant", n => fn.constant() ,
         "expr", n => fn.expr() ,
         "exprmember", n => fn.exprmember() ,
         "callermember", n => fn.callermember() ,
    .Vs("Method name retrieval");


// Define other methods and classes here
class methods 
    public string reflection() 
        return System.Reflection.MethodBase.GetCurrentMethod().Name;
    
    public string stacktrace() 
        return new StackTrace().GetFrame(0).GetMethod().Name;
    
    public string inlineconstant() 
        return "inlineconstant";
    
    const string CONSTANT_NAME = "constant";
    public string constant() 
        return CONSTANT_NAME;
    
    public string expr() 
        Expression<Func<methods, string>> ex = e => e.expr();
        return ex.ToString();
    
    public string exprmember() 
        return expressionName<methods,string>(e => e.exprmember);
    
    protected string expressionName<T,P>(Expression<Func<T,Func<P>>> action) 
        // https://***.com/a/9015598/1037948
        return ((((action.Body as UnaryExpression).Operand as MethodCallExpression).Object as ConstantExpression).Value as MethodInfo).Name;
    
    public string callermember([CallerMemberName]string name = null) 
        return name;
    

结果

反思 反思

堆栈跟踪 堆栈跟踪

内联常量 内联常量

常数 常数

表达式 e => e.expr()

exprmember 表达式成员

来电成员 主要的

Method name retrieval: (reflection) vs (stacktrace) vs (inlineconstant) vs (constant) vs (expr) vs (exprmember) vs (callermember) 

 154673 ticks elapsed ( 15.4673 ms) - reflection
2588601 ticks elapsed (258.8601 ms) - stacktrace
   1985 ticks elapsed (  0.1985 ms) - inlineconstant
   1385 ticks elapsed (  0.1385 ms) - constant
1366706 ticks elapsed (136.6706 ms) - expr
 775160 ticks elapsed ( 77.516  ms) - exprmember
   2073 ticks elapsed (  0.2073 ms) - callermember


>> winner: constant

请注意,exprcallermember 方法并不完全“正确”。在那里你会看到 a related comment 的重复,反射比堆栈跟踪快约 15 倍。

【讨论】:

【参考方案7】:

编辑:MethodBase 可能是获取您所在方法的更好方法(而不是整个调用堆栈)。不过,我仍然会担心内联。

您可以在方法中使用 StackTrace:

StackTrace st = new StackTrace(true);

看看框架:

// The first frame will be the method you want (However, see caution below)
st.GetFrames();

但是,请注意,如果方法是内联的,您将不会在您认为的方法中。您可以使用属性来防止内联:

[MethodImpl(MethodImplOptions.NoInlining)]

【讨论】:

由于发布优化而导致的内联特别棘手,因为代码在调试和发布配置中的行为会有所不同。小心小型房产,它们最有可能成为受害者。 我想知道你为什么要使用new StackTrace(true) 而不是new StackTrace(false)。将其设置为true 将导致堆栈跟踪尝试捕获文件名、行号等,这可能会使此调用变慢。否则,一个很好的答案【参考方案8】:

简单的处理方式是:

System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.FullName + "." + System.Reflection.MethodBase.GetCurrentMethod().Name;

如果 System.Reflection 包含在 using 块中:

MethodBase.GetCurrentMethod().DeclaringType.FullName + "." + MethodBase.GetCurrentMethod().Name;

【讨论】:

【参考方案9】:

这个怎么样:

StackFrame frame = new StackFrame(1);
frame.GetMethod().Name; //Gets the current method name

MethodBase method = frame.GetMethod();
method.DeclaringType.Name //Gets the current class name

【讨论】:

【参考方案10】:

对于Async方法,可以使用:

//using System.Reflection;

var myMethodName = MethodBase
                    .GetCurrentMethod()
                    .DeclaringType
                    .Name
                    .Substring(1)
                    .Split('>')[0];

【讨论】:

在***.com/questions/2968352/…中描述了支持普通和异步方法的类似实现【参考方案11】:

为了处理异步和普通的旧方法调用,我这样做了。

在我的应用程序中,它只是从异常处理程序中调用,所以性能不是问题。

[MethodImpl(MethodImplOptions.NoInlining)]
public static string GetCurrentMethodName()

    var st = new StackTrace();
    var sf = st.GetFrame(1);
    string name = sf.GetMethod().Name;

    if (name.Equals("MoveNext"))
    
        // We're inside an async method
        name = sf.GetMethod().ReflectedType.Name
                 .Split(new char[]  '<', '>' , StringSplitOptions.RemoveEmptyEntries)[0];
    

    return name;

【讨论】:

【参考方案12】:

我认为您应该能够通过创建StackTrace 来实现这一点。或者,正如@edg 和@Lars Mæhlum 提到的,MethodBase.GetCurrentMethod()

【讨论】:

【参考方案13】:
using System;
                    
public class Program

    public static void Main()
    
        
        Console.WriteLine("1: 0 1", System.Reflection.MethodBase.GetCurrentMethod().Name, System.Reflection.MethodBase.GetCurrentMethod().ReflectedType);
        OtherMethod();
    
    
    public static void OtherMethod()
    
        Console.WriteLine("2: 0 1", System.Reflection.MethodBase.GetCurrentMethod().Name, System.Reflection.MethodBase.GetCurrentMethod().ReflectedType);
    

输出:

1: Main Program
2: OtherMethod Program

【讨论】:

【参考方案14】:

我只是用一个简单的静态类做到了这一点:

using System.Runtime.CompilerServices;
.
.
.
public static class MyMethodName

    public static string Show([CallerMemberName] string name = "")
    
        return name;
    

然后在您的代码中:

private void button1_Click(object sender, EventArgs e)

    textBox1.Text = MyMethodName.Show();


private void button2_Click(object sender, EventArgs e)

    textBox1.Text = MyMethodName.Show();

【讨论】:

【参考方案15】:

从 2021 年开始为客户提供更具弹性的解决方案:

namespace my 
   public struct notmacros
   

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string 
  whoami( [CallerMemberName] string caller_name = null)
  
     if (string.IsNullOrEmpty(caller_name)) 
        return "unknown";
     if (string.IsNullOrWhiteSpace(caller_name)) 
        return "unknown";
     return caller_name;
  
 
 // my namespace

用法

using static my.notmacros
 // somewhere  appropriate
 var my_name = whoami() ;

实际演示的.NET小提琴链接:

https://dotnetfiddle.net/moK73n

请注意编译器要求:.NET 6

【讨论】:

【参考方案16】:

试试这个...

    /// <summary>
    /// Return the full name of method
    /// </summary>
    /// <param name="obj">Class that calls this method (use Report(this))</param>
    /// <returns></returns>
    public string Report(object obj)
    
        var reflectedType = new StackTrace().GetFrame(1).GetMethod().ReflectedType;
        if (reflectedType == null) return null;

        var i = reflectedType.FullName;
        var ii = new StackTrace().GetFrame(1).GetMethod().Name;

        return string.Concat(i, ".", ii);
    

【讨论】:

【参考方案17】:
new StackTrace().ToString().Split("\r\n",StringSplitOptions.RemoveEmptyEntries)[0].Replace("at ","").Trim()

【讨论】:

【参考方案18】:

将此方法添加到某处并在没有参数的情况下调用它!

public static string GetCurrentMethodName([System.Runtime.CompilerServices.CallerMemberName] string name = "")

    return name;

【讨论】:

以上是关于能不能用反射来找到当前正在执行的方法的名字?的主要内容,如果未能解决你的问题,请参考以下文章

python的反射机制

android 如何获取手机当前的Activity实例

C#反射执行方法返回List,怎么获取List

linux当前目录下的可执行文件不能直接执行的原因

sql2019建完表不能执行

JAVA中子类能不能继承父类的私有属性和方法?