Как я могу найти метод, который вызвал текущий метод?
При входе в 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);
В 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 раз быстрее.
Краткий обзор двух подходов, где сравнение скорости является важной частью.
Определение вызывающего во время компиляции
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
объект. Однако есть некоторые предостережения в отношении этого подхода:
- Он представляет собой стек времени выполнения - оптимизации могут встроить метод, и вы не увидите этот метод в трассировке стека.
- Он не будет отображать какие-либо собственные кадры, поэтому, если есть даже шанс, что ваш метод вызывается собственным методом, это не сработает, и на самом деле в настоящее время нет никакого способа сделать это.
(ПРИМЕЧАНИЕ: я только расширяю ответ, предоставленный Фирасом Асадом.)
Начиная с.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".
Рекомендации
- Microsoft - Информация о вызывающем абоненте (C#)
- Microsoft -
CallerFilePathAttribute
Учебный класс - Microsoft -
CallerLineNumberAttribute
Учебный класс - 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 Лямбда-имена не генерируются случайным образом. Правило выглядит так:>
private MethodInfo GetCallingMethodInfo(string funcName)
{
return GetType().GetMethod(
funcName.Substring(1,
funcName.IndexOf(">", 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;