如何找到调用当前方法的方法?


503

登录C#时,如何得知调用当前方法的方法的名称?我了解的全部System.Reflection.MethodBase.GetCurrentMethod(),但是我想在堆栈跟踪中比这低一级。我已经考虑过解析堆栈跟踪,但是我希望找到一种更清晰的方法,类似于Assembly.GetCallingAssembly()方法。


22
如果您使用的是.net 4.5 beta +,则可以使用CallerInformation API
罗希特·夏尔马

5
来电者信息也要快得多
2012年

4
我创建了三种主要方法(和)的快速BenchmarkDotNet基准测试StackTraceStackFrame并将CallerMemberName结果作为要点发布,供其他人在此处查看:gist.github.com/wilson0x4d/7b30c3913e74adf4ad99b09163a57a1f
Shaun Wilson

Answers:


512

尝试这个:

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

它来自使用反射[C#]的Get调用方法


12
您也可以只创建所需的框架,而不必创建整个堆栈:
Joel Coehoorn,

187
新的StackFrame(1).GetMethod()。Name;
Joel Coehoorn

12
但是,这并不完全可靠。让我们看看它是否在评论中起作用!在控制台应用程序中尝试以下操作,您会发现编译器优化破坏了它。静态void Main(string [] args){CallIt(); }私有静态void CallIt(){Final(); } static void Final(){StackTrace trace = new StackTrace(); StackFrame框架= trace.GetFrame(1); Console.WriteLine(“ {0}。{1}()”,frame.GetMethod()。DeclaringType.FullName,frame.GetMethod()。Name); }
BlackWasp

10
当编译器内联或尾调用优化该方法时,此方法不起作用,在这种情况下,堆栈会折叠,并且您会发现其他值超出预期。当您仅在Debug版本中使用此功能时,它会很好地工作。
亚伯(Abel)2012年

46
我过去所做的就是在将要查找堆栈跟踪的方法之前添加编译器属性[MethodImplAttribute(MethodImplOptions.NoInlining)]。这样可以确保编译器不会内联该方法,并且堆栈跟踪将包含真正的调用方法(在大多数情况下,我不担心尾递归。)
Jordan Rieger 2012年

363

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

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

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


13
您好,它不是C#5,它在4.5中可用。
AFract

35
@AFract语言(C#)版本与.NET版本不同。
kwesolowski

6
@stuartd看起来[CallerTypeName]是从当前的.Net框架(4.6.2)和Core CLR中
删除的

4
@ Ph0en1x从来没有在框架中,我的意思是,如果可以的话,它将很方便,例如,如何获取CallerMember的类型名称
stuartd

3
@DiegoDeberdt-我已经读到,使用它没有任何反射缺点,因为它在编译时完成了所有工作。我相信所谓的方法是准确的。
cchamberlain

109

您可以使用“呼叫者信息”和可选参数:

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获得
DerApe 2015年

1
请注意,如果调用者传递了一个错误:CachingHelpers.WhoseThere("wrong name!");==> ,这将不起作用,"wrong name!"因为CallerMemberName只能替换默认值。
奥利维尔·雅各布·德斯科姆斯

@ OlivierJacot-Descombes不能以这种方式工作,就像扩展方法无法通过它传递参数一样。您虽然可以使用另一个字符串参数。另请注意,如果您尝试像以前那样传递参数,则reshaper会给您警告。
2013年

1
@dove您可以将任何显式this参数传递给扩展方法。同样,奥利维尔(Olivier)是正确的,您可以传递一个值并且[CallerMemberName]不应用;相反,它用作替代,通常将使用默认值。事实上,如果我们看一下IL,我们可以看到生成的方法与通常为[opt]arg 发出的方法没有什么不同,因此注入CallerMemberName是CLR行为。最后,文档:“呼叫者信息属性会影响省略该参数时传递的默认值
Shaun Wilson

2
这是完美和async友好的,StackFrame不会帮您的忙。也不会影响从lambda调用。
亚伦

65

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

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中使用,而不会出现命名空间不可用的问题。
lyndon hughey

63

通过仅实例化我们实际需要的框架而不是整个堆栈,我们可以对Assad先生的代码(当前接受的答案)进行一些改进:

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

这可能会更好一些,尽管很可能仍然必须使用完整的堆栈来创建单个帧。同样,它仍然具有Alex Lyman指出的警告(优化器/本地代码可能会破坏结果)。最后,您可能要检查以确保new StackFrame(1).GetFrame(1)不返回null,似乎不太可能。

看到这个相关的问题: 可以使用反射来查找当前正在执行的方法的名称吗?


1
甚至有可能new ClassName(…)等于null?
显示名称

1
很好的是,它也可以在.NET Standard 2.0中使用。
srsedate

60

通常,您可以使用System.Diagnostics.StackTrace类获取System.Diagnostics.StackFrame,然后使用GetMethod()方法获取System.Reflection.MethodBase对象。但是,此方法有一些警告

  1. 它代表运行时堆栈-优化可以内联一个方法,并且您不会在堆栈跟踪中看到该方法。
  2. 不会显示任何本机框架,因此,即使有可能您的方法被本机方法调用,也将无法正常工作,并且实际上目前尚无可用的方法。

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


2
在关闭优化的调试模式下,您是否可以看到堆栈跟踪中的方法?
AttackingHobo

1
@AttackingHobo:是的-除非内联(优化)该方法或本机框架,否则您将看到它。
Alex Lyman

38

从.NET 4.5开始,您可以使用“ 呼叫者信息”属性:

  • 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-呼叫者信息(C#)
  2. Microsoft- CallerFilePathAttribute
  3. Microsoft- CallerLineNumberAttribute
  4. Microsoft- CallerMemberNameAttribute

15

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

考虑像PostSharp这样的面向方面的编程(AOP),而不是从您的代码中调用它,而是修改您的代码,从而始终知道它在哪里。


您完全正确地说,这在发行中将不起作用。我不确定我是否喜欢代码注入的想法,但是从某种意义上讲,我认为调试语句需要修改代码,但仍然如此。为什么不回到C宏呢?至少您可以看到。
ebyrob

9

显然,这是一个较晚的答案,但是如果您可以使用.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");

8
/// <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”)。
法兰德斯

6

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

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

4
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帧”也很容易返回方法调用。
user2864740 '16

2

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

如果只需要调用者/上下文进行开发,则可以param在发货前将其删除。


2

要获取方法名称和类名称,请尝试以下操作:

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

1
StackFrame caller = (new System.Diagnostics.StackTrace()).GetFrame(1);
string methodName = caller.GetMethod().Name;

我认为就足够了。



1

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

假设您有一个定义的方法:

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

而您想找到它的呼叫者。

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

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

2。Lambda名称不是随机生成的。规则似乎是:> <CallerMethodName> __ 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);

0

有关Firas Assaad答案的更多信息。

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

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


-1
var callingMethod = new StackFrame(1, true).GetMethod();
string source = callingMethod.ReflectedType.FullName + ": " + callingMethod.Name;

1
我没有拒绝投票,但想指出的是,添加一些文字来解释为什么您发布了非常相似的信息(几年后)可能会增加问题的价值并避免进一步投票。
肖恩·威尔逊
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.