Як я можу знайти метод, який викликав поточний метод?


503

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


22
Якщо ви використовуєте .net 4.5 beta +, ви можете використовувати CallerInformation API .
Рохіт Шарма

5
Інформація про абонентів також набагато швидша
голуб

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

Відповіді:


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

Це з методу Get Calling за допомогою Reflection [C #] .


12
Ви також можете створити лише потрібний кадр, а не весь стек:
Joel Coehoorn

187
новий StackFrame (1) .GetMethod (). ім'я;
Joel Coehoorn

12
Це не зовсім надійно. Подивимось, чи працює це в коментарі! Спробуйте наступне в консольній програмі, і ви бачите, що оптимізації компілятора порушують це. static void Main (string [] args) {CallIt (); } приватна статична недійсність CallIt () {Final (); } static void Final () {слід StackTrace = новий StackTrace (); StackFrame frame = trace.GetFrame (1); Console.WriteLine ("{0}. {1} ()", frame.GetMethod (). DeclaringType.FullName, frame.GetMethod (). Ім'я); }
BlackWasp

10
Це не працює, коли компілятор inlines або хвостовий виклик оптимізує метод, і в цьому випадку стек згортається, і ви знайдете інші значення, ніж очікувалося. Якщо ви використовуєте це лише в налагодженнях налагодження, він буде добре працювати.
Авель

46
Що раніше я робив, це додати атрибут компілятора [MethodImplAttribute (MethodImplOptions.NoInlining)] перед методом, який шукатиме трасування стека. Це гарантує, що компілятор не буде вводити метод в лінію, і слід стека буде містити справжній метод виклику (я не переживаю за рекурсію хвоста у більшості випадків.)
Jordan Rieger,

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.
АФРАКТ

35
Версії @AFract Language (C #) не такі, як версія .NET.
kwesolowski

6
@stuartd Схоже, [CallerTypeName]випав із поточної .Net Framework (4.6.2) та Core CLR
Ph0en1x

4
@ Ph0en1x це ніколи не було в рамках, мою думку, було б корисно, якби це було, наприклад, як отримати ім'я типу CallerMember
stuartd

3
@DiegoDeberdt - Я читав, що використання цього методу не має жодних недоліків відображення, оскільки він робить всю роботу під час компіляції. Я вважаю, що це точно, що називається методом.
Чемберлен

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!"тому, що CallerMemberNameце лише підміняє значення за замовчуванням.
Олів'є Якот-Дескомбс

@ OlivierJacot-Descombes не працює таким чином, так як метод розширення не працював, якби ви передали йому параметр. ви можете хоч інший параметр рядка, який можна використовувати. Також зауважте, що повторна реалізація попередить вас, якщо ви спробували передати аргумент, як ви.
голуб

1
@dove ви можете передати будь-який явний thisпараметр у метод розширення. Також Олів'є правильний, ви можете передавати значення і [CallerMemberName]не застосовується; натомість він функціонує як переопределення, де зазвичай використовується стандартне значення. Власне кажучи, якщо ми подивимось на ІЛ, то побачимо, що отриманий метод не відрізняється від того, що зазвичай було б випромінено для [opt]аргументу, тому ін'єкція CallerMemberName- це поведінка CLR. Нарешті, документи: "Атрибути інформації про виклику [...] впливають на значення за замовчуванням, яке передається, коли аргумент опущено "
Shaun Wilson

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

Порівняння двох підходів

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

Отже, бачите, використання атрибутів набагато, набагато швидше! Майже на 25 разів швидше.


Цей метод, здається, є вищим підходом. Він також працює в 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". тож ви б назвали GetCallingMethod ("журнал"). -Черс
Фландрія,

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 кадри" може легко повернутись також до викликів методів.
користувач2864740

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

Ми також можемо використовувати лямбда, щоб знайти абонента.

Припустимо, у вас визначений метод:

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

і ви хочете знайти його абонента.

1 . Змініть підпис методу, щоб у нас був параметр типу Action (функція також буде працювати):

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

0

Додаткова інформація до відповіді Фіраса Асада.

Я звик 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
я не подав заявки, але хотів зазначити, що додавання тексту, щоб пояснити, чому ви розмістили дуже подібну інформацію (років по тому), може збільшити значення запитання і уникнути подальшого повторного зволікання.
Шон Вілсон
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.