【问题标题】:How can I find the method that called the current method?如何找到调用当前方法的方法?
【发布时间】:2010-09-15 09:04:15
【问题描述】:

登录C#时,如何知道调用当前方法的方法名?我对System.Reflection.MethodBase.GetCurrentMethod() 了如指掌,但我想在堆栈跟踪中更进一步。我考虑过解析堆栈跟踪,但我希望找到一种更清晰更明确的方式,比如Assembly.GetCallingAssembly(),但用于方法。

【问题讨论】:

  • 如果您使用.net 4.5 beta +,您可以使用CallerInformation API
  • 来电信息也多faster
  • 我为三种主要方法(StackTraceStackFrameCallerMemberName)创建了一个快速的BenchmarkDotNet 基准测试,并将结果发布为其他人在此处查看的要点:gist.github.com/wilson0x4d/7b30c3913e74adf4ad99b09163a57a1f
  • 如果您想在不运行的情况下找到调用方法的位置,请记住,如果通过反射调用该方法,则 Shift+F12 不起作用。有时您必须使用 Ctrl+F 来搜索方法名称字符串。

标签: c# .net logging stack-trace system.diagnostics


【解决方案1】:

试试这个:

using System.Diagnostics;
// Get call stack
StackTrace stackTrace = new StackTrace(); 
// Get calling method name
Console.WriteLine(stackTrace.GetFrame(1).GetMethod().Name);

单行:

(new System.Diagnostics.StackTrace()).GetFrame(1).GetMethod().Name

来自Get Calling Method using Reflection [C#]

【讨论】:

  • 你也可以只创建你需要的框架,而不是整个堆栈:
  • new StackFrame(1).GetMethod().Name;
  • 这并不完全可靠。让我们看看这是否适用于评论!在控制台应用程序中尝试以下操作,您会发现编译器优化会破坏它。静态无效主要(字符串[] args){CallIt(); } 私有静态无效 CallIt() { Final(); } static void Final() { StackTrace trace = new StackTrace(); StackFrame frame = trace.GetFrame(1); Console.WriteLine("{0}.{1}()", frame.GetMethod().DeclaringType.FullName, frame.GetMethod().Name); }
  • 这在编译器内联或尾调用优化方法时不起作用,在这种情况下堆栈被折叠,您会发现其他值超出预期。当你只在调试版本中使用它时,它会很好地工作。
  • 我过去所做的是在将查找堆栈跟踪的方法之前添加编译器属性[MethodImplAttribute(MethodImplOptions.NoInlining)]。这确保编译器不会内联该方法,并且堆栈跟踪将包含真正的调用方法(在大多数情况下我不担心尾递归。)
【解决方案2】:

在 C# 5 中,您可以使用 caller info 获取该信息:

//using System.Runtime.CompilerServices;
public void SendError(string Message, [CallerMemberName] string callerName = "") 
{ 
    Console.WriteLine(callerName + "called me."); 
} 

您还可以获取[CallerFilePath][CallerLineNumber]

【讨论】:

  • 您好,它不是 C# 5,它在 4.5 中可用。
  • @AFract 语言 (C#) 版本与 .NET 版本不同。
  • @stuartd 看起来 [CallerTypeName] 已从当前的 .Net 框架 (4.6.2) 和 Core CLR 中删除
  • @Ph0en1x 它从未在框架中,我的意思是如果它在框架中会很方便,例如how to get Type name of a CallerMember
  • @DiegoDeberdt - 我读到使用它没有反射缺点,因为它在编译时完成所有工作。我相信所谓的方法是准确的。
【解决方案3】:

您可以使用来电者信息和可选参数:

public static string WhoseThere([CallerMemberName] string memberName = "")
{
       return memberName;
}

这个测试说明了这一点:

[Test]
public void Should_get_name_of_calling_method()
{
    var methodName = CachingHelpers.WhoseThere();
    Assert.That(methodName, Is.EqualTo("Should_get_name_of_calling_method"));
}

虽然 StackTrace 在上面运行得非常快,并且在大多数情况下不会成为性能问题,但调用者信息仍然快得多。在 1000 次迭代的样本中,我将其计时速度提高了 40 倍。

【讨论】:

  • 仅适用于 .Net 4.5
  • 请注意,如果调用者传递了一个 agrument,这不起作用:CachingHelpers.WhoseThere("wrong name!"); ==> "wrong name!" 因为CallerMemberName 仅替换默认值。
  • @OlivierJacot-Descombes 无法以这种方式工作,就像如果您将参数传递给扩展方法将无法工作一样。您可以使用另一个可以使用的字符串参数。另请注意,如果您尝试像以前那样传递参数,resharper 会给您一个警告。
  • @dove 您可以将任何显式this 参数传递给扩展方法。此外,Olivier 是正确的,您可以传递一个值并且 [CallerMemberName] 不适用;相反,它充当通常使用默认值的覆盖。事实上,如果我们查看 IL,我们可以看到生成的方法与通常为 [opt] arg 发出的方法没有什么不同,因此CallerMemberName 的注入是 CLR 行为。最后,文档:“调用者信息属性 [...] 影响参数被省略时传入的默认值
  • 这是完美的,async 友好,StackFrame 不会帮助你。也不影响从 lambda 调用。
【解决方案4】:

快速回顾这两种方法,其中速度比较是重要部分。

http://geekswithblogs.net/BlackRabbitCoder/archive/2013/07/25/c.net-little-wonders-getting-caller-information.aspx

在编译时确定调用者

static void Log(object message, 
[CallerMemberName] string memberName = "",
[CallerFilePath] string fileName = "",
[CallerLineNumber] int lineNumber = 0)
{
    // we'll just use a simple Console write for now    
    Console.WriteLine("{0}({1}):{2} - {3}", fileName, lineNumber, memberName, message);
}

使用堆栈确定调用者

static void Log(object message)
{
    // frame 1, true for source info
    StackFrame frame = new StackFrame(1, true);
    var method = frame.GetMethod();
    var fileName = frame.GetFileName();
    var lineNumber = frame.GetFileLineNumber();

    // we'll just use a simple Console write for now    
    Console.WriteLine("{0}({1}):{2} - {3}", fileName, lineNumber, method.Name, message);
}

两种方法的比较

Time for 1,000,000 iterations with Attributes: 196 ms
Time for 1,000,000 iterations with StackTrace: 5096 ms

所以你看,使用属性要快得多!将近 25 倍 事实上更快。

【讨论】:

  • 这种方法似乎是更好的方法。它也可以在 Xamarin 中运行,而不会出现命名空间不可用的问题。
【解决方案5】:

我们可以通过仅实例化我们实际需要的帧而不是整个堆栈来稍微改进阿萨德先生的代码(当前接受的答案):

new StackFrame(1).GetMethod().Name;

这可能会表现得更好一些,但很可能它仍然必须使用完整的堆栈来创建单个帧。此外,它仍然有 Alex Lyman 指出的相同警告(优化器/本机代码可能会破坏结果)。最后,您可能需要检查以确保 new StackFrame(1).GetFrame(1) 不会返回 null,尽管这种可能性看起来不太可能。

查看这个相关问题: Can you use reflection to find the name of the currently executing method?

【讨论】:

  • new ClassName(…) 是否有可能等于 null?
  • 好在这也适用于 .NET Standard 2.0。
【解决方案6】:

一般情况下,可以使用System.Diagnostics.StackTrace类获取System.Diagnostics.StackFrame,然后使用GetMethod()方法获取System.Reflection.MethodBase对象。但是,有some caveats这种方法:

  1. 它代表运行时堆栈——优化可以内联一个方法,您将在堆栈跟踪中看到该方法。
  2. 它将显示任何本机框架,因此,如果您的方法甚至有可能被本机方法调用,这将不会 工作,事实上目前没有可用的方法来做到这一点。

注意:我只是在扩展 Firas Assad 提供的 the answer。)

【讨论】:

  • 在关闭优化的调试模式下,你能看到堆栈跟踪中的方法是什么吗?
  • @AttackingHobo:是的——除非方法是内联的(优化)或原生框架,否则你会看到它。
【解决方案7】:

从 .NET 4.5 开始,您可以使用 Caller Information 属性:

  • CallerFilePath - 调用函数的源文件;
  • CallerLineNumber - 调用函数的代码行;
  • CallerMemberName - 调用函数的成员。

    public void WriteLine(
        [CallerFilePath] string callerFilePath = "", 
        [CallerLineNumber] long callerLineNumber = 0,
        [CallerMemberName] string callerMember= "")
    {
        Debug.WriteLine(
            "Caller File Path: {0}, Caller Line Number: {1}, Caller Member: {2}", 
            callerFilePath,
            callerLineNumber,
            callerMember);
    }
    

 

此功能也存在于“.NET Core”和“.NET Standard”中。

参考文献

  1. Microsoft - Caller Information (C#)
  2. Microsoft - CallerFilePathAttribute Class
  3. Microsoft - CallerLineNumberAttribute Class
  4. Microsoft - CallerMemberNameAttribute Class

【讨论】:

    【解决方案8】:

    显然这是一个较晚的答案,但如果您可以使用 .NET 4.5 或更高版本,我有更好的选择:

    internal static void WriteInformation<T>(string text, [CallerMemberName]string method = "")
    {
        Console.WriteLine(DateTime.Now.ToString() + " => " + typeof(T).FullName + "." + method + ": " + text);
    }
    

    这将打印当前日期和时间,然后是“Namespace.ClassName.MethodName”并以“: text”结尾。
    示例输出:

    6/17/2016 12:41:49 PM => WpfApplication.MainWindow..ctor: MainWindow initialized
    

    使用示例:

    Logger.WriteInformation<MainWindow>("MainWindow initialized");
    

    【讨论】:

      【解决方案9】:

      请注意,由于优化,这样做在发布代码中是不可靠的。此外,在沙盒模式(网络共享)下运行应用程序根本不允许您抓取堆栈帧。

      考虑aspect-oriented programming (AOP),例如PostSharp,它不是从您的代码中调用,而是修改您的代码,因此始终知道它在哪里。

      【讨论】:

      • 你是绝对正确的,这在发行版中不起作用。我不确定我是否喜欢代码注入的想法,但我想在某种意义上调试语句需要代码修改,但仍然如此。为什么不直接回到 C 宏?至少你可以看到。
      【解决方案10】:
      /// <summary>
      /// Returns the call that occurred just before the "GetCallingMethod".
      /// </summary>
      public static string GetCallingMethod()
      {
         return GetCallingMethod("GetCallingMethod");
      }
      
      /// <summary>
      /// Returns the call that occurred just before the the method specified.
      /// </summary>
      /// <param name="MethodAfter">The named method to see what happened just before it was called. (case sensitive)</param>
      /// <returns>The method name.</returns>
      public static string GetCallingMethod(string MethodAfter)
      {
         string str = "";
         try
         {
            StackTrace st = new StackTrace();
            StackFrame[] frames = st.GetFrames();
            for (int i = 0; i < st.FrameCount - 1; i++)
            {
               if (frames[i].GetMethod().Name.Equals(MethodAfter))
               {
                  if (!frames[i + 1].GetMethod().Name.Equals(MethodAfter)) // ignores overloaded methods.
                  {
                     str = frames[i + 1].GetMethod().ReflectedType.FullName + "." + frames[i + 1].GetMethod().Name;
                     break;
                  }
               }
            }
         }
         catch (Exception) { ; }
         return str;
      }
      

      【讨论】:

      • 哎呀,我应该更好地解释“MethodAfter”参数。因此,如果您在“log”类型的函数中调用此方法,您将希望在“log”函数之后获取该方法。所以你会调用 GetCallingMethod("log")。 -干杯
      【解决方案11】:

      也许你正在寻找这样的东西:

      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
      

      【讨论】:

        【解决方案12】:
        private static MethodBase GetCallingMethod()
        {
          return new StackFrame(2, false).GetMethod();
        }
        
        private static Type GetCallingType()
        {
          return new StackFrame(2, false).GetMethod().DeclaringType;
        }
        

        精彩课程在这里:http://www.csharp411.com/c-get-calling-method/

        【讨论】:

        • StackFrame 不可靠。上升“2 帧”也可能很容易返回方法调用。
        【解决方案13】:

        要获取方法名和类名,试试这个:

            public static void Call()
            {
                StackTrace stackTrace = new StackTrace();
        
                var methodName = stackTrace.GetFrame(1).GetMethod();
                var className = methodName.DeclaringType.Name.ToString();
        
                Console.WriteLine(methodName.Name + "*****" + className );
            }
        

        【讨论】:

          【解决方案14】:

          我使用的另一种方法是向相关方法添加参数。例如,使用 void Foo(string context) 代替 void Foo()。然后传入一些表示调用上下文的唯一字符串。

          如果只需要调用者/上下文进行开发,可以在发货前去掉param

          【讨论】:

            【解决方案15】:
            StackFrame caller = (new System.Diagnostics.StackTrace()).GetFrame(1);
            string methodName = caller.GetMethod().Name;
            

            我想就够了。

            【讨论】:

              【解决方案16】:

              看看 Logging method name in .NET。小心在生产代码中使用它。 StackFrame 可能不可靠...

              【讨论】:

              • 内容摘要最好。
              【解决方案17】:

              我们也可以使用 lambda 来找到调用者。

              假设你有一个你自己定义的方法:

              public void MethodA()
                  {
                      /*
                       * Method code here
                       */
                  }
              

              你想找到它的来电者。

              1。更改方法签名,以便我们有一个 Action 类型的参数(Func 也可以):

              public void MethodA(Action helperAction)
                      {
                          /*
                           * Method code here
                           */
                      }
              

              2。 Lambda 名称不是随机生成的。规则似乎是:> __X 其中 CallerMethodName 被前一个函数替换,X 是一个索引。

              private MethodInfo GetCallingMethodInfo(string funcName)
                  {
                      return GetType().GetMethod(
                            funcName.Substring(1,
                                              funcName.IndexOf("&gt;", 1, StringComparison.Ordinal) - 1)
                            );
                  }
              

              3。当我们调用 MethodA 时,Action/Func 参数必须由调用方方法生成。 示例:

              MethodA(() => {});
              

              4。在 MethodA 中,我们现在可以调用上面定义的辅助函数并找到调用者方法的 MethodInfo。

              例子:

              MethodInfo callingMethodInfo = GetCallingMethodInfo(serverCall.Method.Name);
              

              【讨论】:

                【解决方案18】:

                Firas Assaad 回答的额外信息。

                我在 .net core 2.1 中使用了 new StackFrame(1).GetMethod().Name; 和依赖注入,我将调用方法作为“开始”。

                我试过[System.Runtime.CompilerServices.CallerMemberName] string callerName = "" 它给了我正确的调用方法

                【讨论】:

                  【解决方案19】:
                  var callingMethod = new StackFrame(1, true).GetMethod();
                  string source = callingMethod.ReflectedType.FullName + ": " + callingMethod.Name;
                  

                  【讨论】:

                  • 我没有投反对票,但想指出,添加一些文字来解释您为什么发布非常相似的信息(多年后)可能会增加问题的价值并避免进一步投反对票。
                  猜你喜欢
                  • 1970-01-01
                  • 1970-01-01
                  • 2021-02-14
                  • 1970-01-01
                  • 1970-01-01
                  • 2014-08-13
                  • 2014-07-17
                  相关资源
                  最近更新 更多