Как я могу найти метод, который вызвал текущий метод?

380

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

  • 18
    Если вы используете .net 4.5 beta +, вы можете использовать CallerInformation API .
  • 4
    Информация о звонящем также намного быстрее
Показать ещё 1 комментарий
Теги:
reflection
logging
stack-trace
system.diagnostics

17 ответов

388
Лучший ответ

Попробуйте следующее:

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

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

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

  • 0
    Это deffo работает, потому что это решение, которое мы использовали на работе ... хотя я не уверен, почему мы это сделали!
  • 11
    Вы также можете создать только нужный вам кадр, а не весь стек:
Показать ещё 9 комментариев
270

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

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

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

  • 49
    [CallerTypeName] будет [CallerTypeName] в некоторых обстоятельствах ..
  • 12
    Здравствуйте, это не C # 5, это доступно в 4.5.
Показать ещё 7 комментариев
86

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

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

  • 3
    должно быть проголосовано чаще, так как это современный путь.
  • 0
    Доступно только из .Net 4.5
Показать ещё 6 комментариев
60

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

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

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

  • 2
    В режиме отладки с отключенными оптимизациями вы сможете увидеть, что метод находится в трассировке стека?
  • 1
    @AttackingHobo: Да - если метод не встроенный (оптимизация включена) или собственный фрейм, вы его увидите.
58

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

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

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

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

  • 1
    Возможно ли, что new ClassName(…) равно нулю?
  • 0
    Что приятно, так это то, что это работает и в .NET Standard 2.0.
52

Быстрое повторение двух подходов, когда сравнение скорости является важной частью.

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

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

  • 0
    Этот метод кажется лучшим подходом. Он также работает в Xamarin без проблем с недоступными пространствами имен.
14

Начиная с.NET 4.5 вы можете использовать атрибуты информации о вызывающем абоненте:

  • CallerFilePath - исходный файл, который CallerFilePath функцию;
  • CallerLineNumber - строка кода, 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 - информация о вызывающем абоненте (С#)
  2. Microsoft - класс CallerFilePathAttribute
  3. Microsoft - класс CallerLineNumberAttribute
  4. Microsoft - класс CallerMemberNameAttribute
13

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

Рассмотрим аспектно-ориентированное программирование (AOP), например PostSharp, который вместо вызова из вашего кода изменяет ваш код и, следовательно, знает, где он всегда.

  • 0
    Вы абсолютно правы, что это не сработает в релизе. Я не уверен, что мне нравится идея внедрения кода, но я думаю, что в некотором смысле оператор отладки требует модификации кода, но все же. Почему бы просто не вернуться к макросам C? По крайней мере, это то, что вы можете увидеть.
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");
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;
}
  • 0
    К сожалению, мне следовало бы объяснить параметр "MethodAfter" немного лучше. Поэтому, если вы вызываете этот метод в функции типа «log», вам нужно получить метод сразу после функции «log». так что вы бы назвали GetCallingMethod ("log"). -Cheers
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/

  • 0
    StackFrame не является надежным. Подняв «2 кадра», можно легко вернуться назад к вызовам методов.
2

Другим подходом, который я использовал, является добавление параметра к рассматриваемому методу. Например, вместо void Foo() используйте void Foo(string context). Затем передайте некоторую уникальную строку, которая указывает контекст вызова.

Если вам нужен только вызывающий/контекстный контекст для разработки, вы можете удалить param перед отправкой.

1

Мы также можем использовать лямбда, чтобы найти вызывающего.

Предположим, что у вас есть метод, определенный вами:

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

и вы хотите найти его вызывающим.

1. Измените сигнатуру метода, так что у нас есть параметр типа Action (Func также будет работать):

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

2. Имена Лямбды не генерируются случайным образом. Правило выглядит следующим образом: > <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);
1

Посмотрите Имя метода ведения журнала в .NET. Остерегайтесь использовать его в производственном коде. StackFrame может быть ненадежным...

  • 4
    Краткое содержание было бы неплохо.
0
StackFrame caller = (new System.Diagnostics.StackTrace()).GetFrame(1);
string methodName = caller.GetMethod().Name;

будет достаточно, я думаю.

-1
var callingMethod = new StackFrame(1, true).GetMethod();
string source = callingMethod.ReflectedType.FullName + ": " + callingMethod.Name;
  • 0
    я не отрицал, но хотел отметить, что добавление некоторого текста, объясняющего, почему вы разместили очень похожую информацию (годы спустя), может повысить ценность вопроса и избежать дальнейшего понижения.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню