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

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

21 ответ

Решение

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

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

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

Это из Get Calling Method, использующего Reflection [C#].

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

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

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

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

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

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

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

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

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

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

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

Начиная с.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 Учебный класс

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

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

Рассмотрим аспектно-ориентированное программирование (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;
}

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

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/

Для получения имени метода и имени класса попробуйте это:

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

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

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

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

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

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

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

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 Лямбда-имена не генерируются случайным образом. Правило выглядит так:> __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);
      public static string GetCallerFullName<T>(this T callingObject,[CallerMemberName] string caller = default) =>$"{callingObject.GetType().Name}:{caller}";

Это небольшое улучшение решения Камило Теревинто, поскольку соглашение о вызовах немного чище.

      this.GetCallerFullName();

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

public enum CALL_TYPE
{
    ClassName,
    MethodName,
    PrevMethodName,
    ModuleName
}

/// <summary>
/// Get full namespace, class, and/or method name.
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
[MethodImpl(MethodImplOptions.NoInlining)]
static public string GetReflection(CALL_TYPE type)
{
    string retVal = "";
    int STACK_FRAME = (type == CALL_TYPE.ClassName ? 1 : (int)type);
    STACK_FRAME = (type == CALL_TYPE.ModuleName ? 1 : STACK_FRAME); 

    try
    {
        StackTrace st = new StackTrace(new StackFrame(STACK_FRAME));
        switch (type)
        {
            case CALL_TYPE.ClassName:
                string[] allData = string.Format("{0}", st.GetFrame(0).GetMethod().ReflectedType.FullName).Split('.');
                retVal = allData[allData.Length - 1];
                break;
            case CALL_TYPE.MethodName:
            case CALL_TYPE.PrevMethodName:
                retVal = string.Format("{0}.{1}", st.GetFrame(0).GetMethod().ReflectedType.FullName, st.GetFrame(0).GetMethod().Name);
                break;
            case CALL_TYPE.ModuleName:
                retVal = st.GetFrame(0).GetMethod().Module.Name;
                break;
        }

    }
    catch
    {
        //unable to retrieve method
        retVal = "";
    }

    return retVal;

}

Называя это:

string currentMethod = GetReflection(CALL_TYPE.MethodName);

или же

string prevtMethod = GetReflection(CALL_TYPE.PrevMethodName);
var callingMethod = new StackFrame(1, true).GetMethod();
string source = callingMethod.ReflectedType.FullName + ": " + callingMethod.Name;
Другие вопросы по тегам