Как я могу найти метод, который вызвал текущий метод? - PullRequest
434 голосов
/ 05 октября 2008

При входе в C #, как узнать имя метода, вызвавшего текущий метод? Я знаю все о System.Reflection.MethodBase.GetCurrentMethod(), но я хочу сделать еще один шаг в трассировке стека. Я рассмотрел анализ трассировки стека, но я надеюсь найти более понятный и понятный способ, например, Assembly.GetCallingAssembly(), но для методов.

Ответы [ 19 ]

441 голосов
/ 05 октября 2008

Попробуйте это:

using System.Diagnostics;
// Get call stack
StackTrace stackTrace = new StackTrace();

// Get calling method name
Console.WriteLine(stackTrace.GetFrame(1).GetMethod().Name);

Это из Получить метод вызова с использованием Reflection [C #] .

302 голосов
/ 08 марта 2012

В C # 5 вы можете получить эту информацию, используя информацию о вызывающем абоненте:

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

Вы также можете получить [CallerFilePath] и [CallerLineNumber].

98 голосов
/ 21 ноября 2012

Вы можете использовать информацию о вызывающем абоненте и дополнительные параметры:

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 раз быстрее.

60 голосов
/ 05 октября 2008

Как правило, вы можете использовать класс System.Diagnostics.StackTrace, чтобы получить System.Diagnostics.StackFrame, а затем использовать метод GetMethod(), чтобы получить System.Reflection.MethodBase объект. Однако есть некоторые предостережения для этого подхода:

  1. Он представляет собой время выполнения стека - оптимизации могут встроить метод, и вы не увидите этот метод в трассировке стека.
  2. Он будет не показывать какие-либо собственные кадры, поэтому, если есть даже шанс, что ваш метод вызывается собственным методом, это будет не работа, и на самом деле в настоящее время нет способа сделать это.

( ПРИМЕЧАНИЕ. Я просто расширяю ответ , предоставленный Firas Assad .)

60 голосов
/ 05 октября 2008

Мы можем немного улучшить код г-на Асада (текущий принятый ответ), создав только тот кадр, который нам нужен, а не весь стек:

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

Это может работать немного лучше, хотя, по всей вероятности, ему все равно придется использовать полный стек для создания этого отдельного кадра. Кроме того, он по-прежнему имеет те же предостережения, на которые указал Алекс Лайман (оптимизатор / собственный код может повредить результаты). Наконец, вы можете убедиться, что new StackFrame(1) или .GetFrame(1) не возвращают null, что маловероятно, как может показаться такая возможность.

См. Этот связанный вопрос: Можете ли вы использовать отражение, чтобы найти имя выполняемого в данный момент метода?

54 голосов
/ 26 ноября 2015

Краткий обзор 2 заходов на посадку, важная часть - сравнение скорости.

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

Сравнение 2 подходов

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

Итак, вы видите, что использование атрибутов намного, намного быстрее! Почти 25x на самом деле быстрее.

25 голосов
/ 20 октября 2015

Начиная с .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 Класс
13 голосов
/ 05 октября 2008

Обратите внимание, что из-за оптимизации это будет ненадежным в коде выпуска. Кроме того, запуск приложения в режиме «песочницы» (сетевой ресурс) вообще не позволит вам захватить кадр стека.

Рассмотрим аспектно-ориентированное программирование (AOP), например PostSharp , которое вместо вызова из вашего кода модифицирует ваш код и, таким образом, всегда знает, где оно находится.

8 голосов
/ 17 июня 2016

Очевидно, что это поздний ответ, но у меня есть лучший вариант, если вы можете использовать .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 голосов
/ 29 мая 2010
/// <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;
}
...