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

503

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

flipdoubt
источник
22
Если вы используете .net 4.5 beta +, вы можете использовать CallerInformation API .
Рохит Шарма
5
Информация о звонящем также намного быстрее
нырнул
4
Я создал быстрый BenchmarkDotNet тест из трех основных методов ( StackTrace, StackFrameа CallerMemberName) и опубликовал результаты как суть для других , чтобы увидеть здесь: gist.github.com/wilson0x4d/7b30c3913e74adf4ad99b09163a57a1f
Shaun Wilson

Ответы:

513

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

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, использующего Reflection [C #] .

Firas Assaad
источник
12
Вы также можете создать именно тот кадр, который вам нужен, а не весь стек:
Joel Coehoorn
188
новый StackFrame (1) .GetMethod (). Name;
Джоэл Коухорн
12
Это не совсем надежно, хотя. Давайте посмотрим, работает ли это в комментарии! Попробуйте следующее в консольном приложении, и вы увидите, что ошибки компилятора нарушают его. static void Main (string [] 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); }
BlackWasp
10
Это не работает, когда компилятор встроенный или хвостовой вызов оптимизирует метод, и в этом случае стек свернут, и вы найдете другие значения, отличные от ожидаемых. Когда вы используете это только в сборках Debug, это будет хорошо работать.
Авель
46
В прошлом я добавил атрибут компилятора [MethodImplAttribute (MethodImplOptions.NoInlining)] перед методом, который будет искать трассировку стека. Это гарантирует, что компилятор не будет встроен в метод, а трассировка стека будет содержать истинный вызывающий метод (в большинстве случаев меня не волнует хвостовая рекурсия.)
Джордан Ригер,
363

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

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

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

Coincoin
источник
13
Здравствуйте, это не C # 5, это доступно в 4.5.
ПОСЛЕ
35
Версии @AFract Language (C #) не совпадают с версией .NET.
kwesolowski
6
@stuartd Похоже, [CallerTypeName]был удален из текущей .Net Framework (4.6.2) и Core CLR
Ph0en1x
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
1
Обратите внимание, что это не работает, если вызывающая сторона передает agrument: CachingHelpers.WhoseThere("wrong name!");==>, "wrong name!"потому что CallerMemberNameis заменяет только значение по умолчанию.
Оливье Жако-
@ OlivierJacot-Descombes не работает таким же образом, как метод расширения не будет работать, если вы передадите ему параметр. Вы можете использовать другой строковый параметр, который можно использовать. Также обратите внимание, что resharper выдаст вам предупреждение, если вы попытаетесь передать аргумент, как вы это сделали.
голубь
1
@Dove вы можете передать любой явный thisпараметр в метод расширения. Кроме того, Оливье правильно, вы можете передать значение и [CallerMemberName]не применяется; вместо этого он работает как переопределение, где обычно используется значение по умолчанию. На самом деле, если мы посмотрим на IL, то увидим, что результирующий метод ничем не отличается от того, который обычно выдается для [opt]аргумента arg, CallerMemberNameпоэтому инъекция - это поведение CLR. Наконец, документы: «Атрибуты Caller Info [...] влияют на значение по умолчанию, которое передается, когда аргумент пропущен »
Шон Уилсон,
2
Это прекрасно и asyncдружелюбно, что вам StackFrameне поможет. Также не влияет на звонки с лямбды.
Аарон
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);
}

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

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

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

Tikall
источник
Этот метод кажется лучшим подходом. Он также работает в Xamarin без проблем с недоступными пространствами имен.
Линдон Хьюи
63

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

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

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

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

Джоэл Коухорн
источник
1
это вообще возможно, что new ClassName(…)равно нулю?
Отображаемое имя
1
Что приятно, так это то, что это работает и в .NET Standard 2.0.
srsedate
60

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

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

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

Алекс Лиман
источник
2
В режиме отладки с отключенными оптимизациями вы сможете увидеть, что метод находится в трассировке стека?
AttackingHobo
1
@AttackingHobo: Да - если метод не встроенный (оптимизация включена) или собственный фрейм, вы увидите его.
Алекс Лиман
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

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

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

Лассе В. Карлсен
источник
Вы абсолютно правы, что это не сработает в релизе. Я не уверен, что мне нравится идея внедрения кода, но я думаю, что в некотором смысле оператор отладки требует модификации кода, но все же. Почему бы просто не вернуться к макросам 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
jesal
источник
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/

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

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

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

GregUzelac
источник
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

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

Юваль Пелед
источник
5
Краткое содержание было бы неплохо.
Питер Мортенсен
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);
smiron
источник
0

Дополнительная информация к ответу Firas Assaad.

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

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

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