При входе на С#, как узнать имя метода, называемого текущим методом? Я знаю все о System.Reflection.MethodBase.GetCurrentMethod()
, но я хочу сделать один шаг ниже этого в трассировке стека. Я рассмотрел разбор трассировки стека, но я надеюсь найти более чистый более явный способ, например Assembly.GetCallingAssembly()
, но для методов.
Попробуйте следующее:
using System.Diagnostics;
// Get call stack
StackTrace stackTrace = new StackTrace();
// Get calling method name
Console.WriteLine(stackTrace.GetFrame(1).GetMethod().Name);
В С# 5 вы можете получить эту информацию, используя информацию о вызывающем абоненте:
//using System.Runtime.CompilerServices;
public void SendError(string Message, [CallerMemberName] string callerName = "")
{
Console.WriteLine(callerName + "called me.");
}
Вы также можете получить [CallerFilePath]
и [CallerLineNumber]
.
[CallerTypeName]
будет [CallerTypeName]
в некоторых обстоятельствах ..
Вы можете использовать информацию о вызывающем абоненте и необязательные параметры:
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
. Однако есть некоторые оговорки к этому подходу:
(ПРИМЕЧАНИЕ. Я просто расширяю ответ, предоставленный Firas Assad.)
Мы можем немного улучшить код г-на Ассада (текущий принятый ответ), создав экземпляр только нужного кадра, а не всего пакета:
new StackFrame(1).GetMethod().Name;
Это может быть немного лучше, хотя, по всей вероятности, ему все равно придется использовать полный стек для создания одного кадра. Кроме того, у него все еще есть те же предостережения, которые указал Алекс Лайман (оптимизатор/собственный код может испортить результаты). Наконец, вы можете проверить, чтобы убедиться, что new StackFrame(1)
или .GetFrame(1)
не возвращают null
, что маловероятно, поскольку эта возможность может показаться.
Смотрите этот вопрос: Можете ли вы использовать отражение, чтобы найти имя текущего исполняемого метода?
new ClassName(…)
равно нулю?
Быстрое повторение двух подходов, когда сравнение скорости является важной частью.
Определение вызывающего абонента во время компиляции
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
Итак, вы видите, что использование атрибутов намного, намного быстрее! Почти 25x быстрее.
Начиная с.NET 4.5 вы можете использовать атрибуты информации о вызывающем абоненте:
CallerFilePath
- исходный файл, который CallerFilePath
функцию;CallerLineNumber
- строка кода, 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".
Рекомендации
Обратите внимание, что выполнение этого будет недостоверным в коде выпуска, из-за оптимизации. Кроме того, запуск приложения в режиме песочницы (сетевой ресурс) не позволит вам полностью захватить фрейм стека.
Рассмотрим аспектно-ориентированное программирование (AOP), например PostSharp, который вместо вызова из вашего кода изменяет ваш код и, следовательно, знает, где он всегда.
Очевидно, что это поздний ответ, но у меня есть лучший вариант, если вы можете использовать .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");
/// <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/
Другим подходом, который я использовал, является добавление параметра к рассматриваемому методу. Например, вместо void Foo()
используйте void Foo(string context)
. Затем передайте некоторую уникальную строку, которая указывает контекст вызова.
Если вам нужен только вызывающий/контекстный контекст для разработки, вы можете удалить param
перед отправкой.
Мы также можем использовать лямбда, чтобы найти вызывающего.
Предположим, что у вас есть метод, определенный вами:
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(">", 1, StringComparison.Ordinal) - 1)
);
}
3. Когда мы вызываем MethodA, параметр Action/Func должен быть сгенерирован методом вызывающего. Пример:
MethodA(() => {});
4. Внутри MethodA мы теперь можем вызвать вспомогательную функцию, определенную выше, и найти MethodInfo метода вызывающего.
Пример:
MethodInfo callingMethodInfo = GetCallingMethodInfo(serverCall.Method.Name);
Посмотрите Имя метода ведения журнала в .NET. Остерегайтесь использовать его в производственном коде. StackFrame может быть ненадежным...
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;