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


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

17 396

17 ответов:

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

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

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

Он с получить вызов метода с помощью отражения [C#].

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

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

вы можете узнать [CallerFilePath] и [CallerLineNumber].

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

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

В общем, вы можете использовать System.Diagnostics.StackTrace класс, чтобы получить System.Diagnostics.StackFrame, а затем использовать GetMethod() метод получить System.Reflection.MethodBase

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

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

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

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

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

начиная с .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" и ".Чистый стандарт".

ссылки

  1. Microsoft-Caller Information (C#)
  2. Microsoft -CallerFilePathAttribute класс
  3. Microsoft -CallerLineNumberAttribute класс
  4. Microsoft -CallerMemberNameAttribute класс

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

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

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

очевидно, что это поздний ответ, но у меня есть лучший вариант, если вы можете использовать .NET 4.5 или более:

internal static void WriteInformation<T>(string text, [CallerMemberName]string method = "")
{
    Console.WriteLine(DateTime.Now.ToString() + " => " + typeof(T).FullName + "." + method + ": " + text);
}

это выведет текущую дату и время, а затем "пространство имен.имя класса.MethodName "и заканчивается на": text".
Пример вывода:

6/17/2016 12:41:49 PM => WpfApplication.MainWindow..ctor: MainWindow initialized

пример использования:

Logger.WriteInformation<MainWindow>("MainWindow initialized");

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

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
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/

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

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

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

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

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

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

и вы хотите найти его абонента.

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

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

2. Лямбда-имена не генерируются случайным образом. Правило выглядит следующим образом: > __X где CallerMethodName заменяется предыдущим функция и X-это индекс.

private MethodInfo GetCallingMethodInfo(string funcName)
    {
        return GetType().GetMethod(
              funcName.Substring(1,
                                funcName.IndexOf("&gt;", 1, StringComparison.Ordinal) - 1)
              );
    }

3. При вызове метода параметр Action/Func должен быть сгенерирован вызывающим методом. Пример:

MethodA(() => {});

4. Внутри MethodA теперь мы можем вызвать вспомогательную функцию, определенную выше, и найти MethodInfo вызывающего метода.

пример:

MethodInfo callingMethodInfo = GetCallingMethodInfo(serverCall.Method.Name);
StackFrame caller = (new System.Diagnostics.StackTrace()).GetFrame(1);
string methodName = caller.GetMethod().Name;

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

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