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

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

Если вы используете .net 4.5 beta +, вы можете использовать CallerInformation API.

Rohit Sharma 11.03.2012 11:17

Информация о вызывающем абоненте тоже много Быстрее

dove 21.11.2012 19:08

Я создал быстрый тест BenchmarkDotNet для трех основных методов (StackTrace, StackFrame и CallerMemberName) и опубликовал результаты, чтобы другие могли увидеть здесь: gist.github.com/wilson0x4d/7b30c3913e74adf4ad99b09163a57a1f

Shaun Wilson 17.03.2018 23:02

На всякий случай, если вы хотите узнать, где вызывается ваш метод, не запуская его, помните, что Shift + F12 не работает, если метод вызывается через Reflection. Иногда вам нужно использовать Ctrl + F для поиска строки имени метода.

David Klempfner 19.01.2021 12:56
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
539
4
261 499
19
Перейти к ответу Данный вопрос помечен как решенный

Ответы 19

Ответ принят как подходящий

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

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#].

Этот deffo работает, потому что это решение, которое мы использовали на работе ... не знаю, почему мы это сделали!

Surgical Coder 05.10.2008 17:36

Вы также можете создать только нужный фрейм, а не весь стек:

Joel Coehoorn 06.10.2008 17:05

новый StackFrame (1) .GetMethod (). Name;

Joel Coehoorn 06.10.2008 17:05

Однако это не совсем надежно. Посмотрим, работает ли это в комментарии! Попробуйте сделать следующее в консольном приложении, и вы увидите, что настройки компилятора нарушают его. static void Main (строка [] аргументы) {CallIt (); } частный статический void CallIt () {Final (); } static void Final () {трассировка StackTrace = новый StackTrace (); StackFrame frame = trace.GetFrame (1); Console.WriteLine ("{0}. {1} ()", frame.GetMethod (). DeclaringType.FullName, frame.GetMethod (). Name); }

BlackWasp 01.06.2009 19:13

Ну что ж, подумал, что это может не понравиться комментариям.

BlackWasp 01.06.2009 19:14

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

Abel 19.03.2012 16:27

В прошлом я добавил атрибут компилятора [MethodImplAttribute (MethodImplOptions.NoInlining)] перед методом, который будет искать трассировку стека. Это гарантирует, что компилятор не будет встроить метод, а трассировка стека будет содержать истинный вызывающий метод (в большинстве случаев я не беспокоюсь о хвостовой рекурсии).

Jordan Rieger 03.08.2012 23:52

Но компилятор может встроить вызывающий метод.

Mikhail Orlov 08.02.2016 12:48

Когда я это делаю, он продолжает возвращать MoveNext. У меня нет метода MoveNext. Это как-то связано с асинхронностью?

Kyle Delaney 09.02.2017 07:47

@MikhailOrlov Я думаю, что если СОБСТВЕННЫЙ КОД вызывающего абонента будет оптимизирован, то, возможно, им следует ожидать здесь некоторой путаницы? По крайней мере, в этот момент они не решают проблемы с чьей-либо библиотекой.

ebyrob 20.06.2017 18:17

Работает хорошо. Остерегайтесь неожиданного поведения при использовании лямбда-выражений, описанных здесь; stackoverflow.com/a/21564897/1146862.

Aaron 06.03.2019 03:43

@KyleDelaney - MoveNext() - это метод, создаваемый компилятором каждый раз, когда вы используете цикл foreach. Я рекомендую вам прочитать этот образец главы из C# в деталях Джона Скита.

Sipo 17.01.2020 02:31

Просто предупреждение: похоже, что это работает и будет работать большую часть времени, но однажды он внезапно начнет возвращать неверные данные без предупреждения. Может быть, новая версия компилятора, может быть новая языковая функция, может быть, новая оптимизация и т. д. Как сказал Эрик в другой ответ: «Цель стека вызовов - сообщить вам куда ты собираешься дальше, а не Откуда ты пришел».

Nick 01.05.2020 01:21

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

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

Вы абсолютно правы, что в выпуске это не сработает. Я не уверен, что мне нравится идея внедрения кода, но я предполагаю, что в некотором смысле оператор отладки требует модификации кода, но все же. Почему бы просто не вернуться к макросам C? Это хоть что-то видно.

ebyrob 20.06.2017 17:56

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

Краткое изложение содержания было бы неплохо.

Peter Mortensen 17.09.2013 23:03

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

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

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

В режиме отладки с отключенной оптимизацией вы сможете увидеть, какой метод находится в трассировке стека?

AttackingHobo 21.01.2011 06:36

@AttackingHobo: Да - если метод не встроен (оптимизация включен) или не является встроенным фреймом, вы его увидите.

Alex Lyman 25.01.2011 22:19

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

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

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

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

возможно ли, чтобы new ClassName(…) был равен нулю?

Display Name 06.03.2015 13:26

Что приятно, так это то, что это работает и в .NET Standard 2.0.

srbrills 07.11.2017 21:15

Может быть, вы ищете что-то вроде этого:

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

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

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

/// <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". Поэтому, если вы вызываете этот метод в функции типа «журнал», вам нужно получить метод сразу после функции «журнала». поэтому вы должны вызвать GetCallingMethod ("log"). -Ваше здоровье

Flanders 29.05.2010 00:10

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 07.11.2016 06:37

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

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

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

Здравствуйте, это не C# 5, он доступен в 4.5.

AFract 16.02.2015 17:06

Версии @AFract Language (C#) не совпадают с версией .NET.

kwesolowski 23.02.2015 00:22

@stuartd Похоже, [CallerTypeName] был удален из текущей среды .Net (4.6.2) и Core CLR

Ph0en1x 13.04.2016 15:30

@ Ph0en1x этого никогда не было во фреймворке, я хотел сказать, что было бы удобно, если бы это было, например как получить имя типа CallerMember

stuartd 13.04.2016 17:24

Есть ли у этого решения те же проблемы с оптимизацией компилятора, что и у решения StackTrace?

Captain Sensible 02.09.2016 12:31

@DiegoDeberdt - я читал, что использование этого не имеет недостатков в отражении, так как он выполняет всю работу во время компиляции. Я считаю, что это верно в отношении того, что называется методом.

cchamberlain 28.05.2017 11:46

У меня это работает, даже если я использую StartCoroutine, IEnumerator в Unity. Спасибо.

CWKSC 17.04.2020 19:07

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

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 06.07.2015 13:41

Обратите внимание, что это не работает, если вызывающий абонент передал аргумент: CachingHelpers.WhoseThere("wrong name!"); ==> "wrong name!", потому что CallerMemberName заменяет только значение по умолчанию.

Olivier Jacot-Descombes 06.12.2017 19:09

@ OlivierJacot-Descombes не работает таким образом, как не работал бы метод расширения, если бы вы передали ему параметр. вы могли бы использовать другой строковый параметр. Также обратите внимание, что resharper выдаст вам предупреждение, если вы попытаетесь передать аргумент, как вы это сделали.

dove 06.12.2017 19:30

@dove вы можете передать любой явный параметр this в метод расширения. Кроме того, Оливье прав, вы можете передать значение, а [CallerMemberName] не применяется; вместо этого он действует как переопределение там, где обычно используется значение по умолчанию. На самом деле, если мы посмотрим на IL, мы увидим, что результирующий метод ничем не отличается от того, который обычно был бы выдан для аргумента [opt], поэтому внедрение CallerMemberName является поведением среды CLR. Наконец, документы: «Атрибуты информации о вызывающем [...] влияет на значение по умолчанию, которое передается, когда аргумент опущен»

Shaun Wilson 17.03.2018 21:35

Это идеально и дружественно к async, с чем StackFrame вам не поможет. Также не влияет на вызов из лямбды.

Aaron 06.03.2019 04:25

Очевидно, это нельзя использовать с конечным объектом params, как в: GetMethodAndInitialParameterValues ​​([CallerMemberName] string memberName = null, params object [] parameterValues) {}

newby 18.06.2020 18:35

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

Думаю, хватит.

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

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

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

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

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

Shaun Wilson 17.03.2018 21: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

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

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

So you see, using the attributes is much, much faster! Nearly 25x faster in fact.

Этот метод кажется более подходящим. Он также работает в Xamarin без проблем с недоступностью пространств имен.

lyndon hughey 19.09.2017 17:18

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

Чтобы получить имя метода и имя класса, попробуйте следующее:

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

Дополнительная информация к ответу Фираса Ассаада.

Я использовал new StackFrame(1).GetMethod().Name; в .net core 2.1 с внедрением зависимостей, и я получаю метод вызова как «Пуск».

Пробовал с [System.Runtime.CompilerServices.CallerMemberName] string callerName = "" и это дает мне правильный метод вызова

Другие вопросы по теме