При входе в C# как я могу узнать имя метода, который вызвал текущий метод? Я знаю все о System.Reflection.MethodBase.GetCurrentMethod(), но я хочу пойти на один шаг ниже в трассировке стека. Я рассматривал возможность синтаксического анализа трассировки стека, но надеюсь найти более понятный способ, например Assembly.GetCallingAssembly(), но для методов.
Информация о вызывающем абоненте тоже много Быстрее
Я создал быстрый тест BenchmarkDotNet для трех основных методов (StackTrace, StackFrame и CallerMemberName) и опубликовал результаты, чтобы другие могли увидеть здесь: gist.github.com/wilson0x4d/7b30c3913e74adf4ad99b09163a57a1f
На всякий случай, если вы хотите узнать, где вызывается ваш метод, не запуская его, помните, что Shift + F12 не работает, если метод вызывается через Reflection. Иногда вам нужно использовать Ctrl + F для поиска строки имени метода.





Попробуй это:
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
Это от Получить метод вызова с использованием отражения [C#].
Этот deffo работает, потому что это решение, которое мы использовали на работе ... не знаю, почему мы это сделали!
Вы также можете создать только нужный фрейм, а не весь стек:
новый StackFrame (1) .GetMethod (). Name;
Однако это не совсем надежно. Посмотрим, работает ли это в комментарии! Попробуйте сделать следующее в консольном приложении, и вы увидите, что настройки компилятора нарушают его. static void Main (строка [] аргументы) {CallIt (); } частный статический void CallIt () {Final (); } static void Final () {трассировка StackTrace = новый StackTrace (); StackFrame frame = trace.GetFrame (1); Console.WriteLine ("{0}. {1} ()", frame.GetMethod (). DeclaringType.FullName, frame.GetMethod (). Name); }
Ну что ж, подумал, что это может не понравиться комментариям.
Это не работает, когда компилятор встроен или хвостовой вызов оптимизирует метод, и в этом случае стек свернут, и вы найдете другие значения, чем ожидалось. Однако, когда вы используете это только в сборках отладки, он будет работать хорошо.
В прошлом я добавил атрибут компилятора [MethodImplAttribute (MethodImplOptions.NoInlining)] перед методом, который будет искать трассировку стека. Это гарантирует, что компилятор не будет встроить метод, а трассировка стека будет содержать истинный вызывающий метод (в большинстве случаев я не беспокоюсь о хвостовой рекурсии).
Но компилятор может встроить вызывающий метод.
Когда я это делаю, он продолжает возвращать MoveNext. У меня нет метода MoveNext. Это как-то связано с асинхронностью?
@MikhailOrlov Я думаю, что если СОБСТВЕННЫЙ КОД вызывающего абонента будет оптимизирован, то, возможно, им следует ожидать здесь некоторой путаницы? По крайней мере, в этот момент они не решают проблемы с чьей-либо библиотекой.
Работает хорошо. Остерегайтесь неожиданного поведения при использовании лямбда-выражений, описанных здесь; stackoverflow.com/a/21564897/1146862.
@KyleDelaney - MoveNext() - это метод, создаваемый компилятором каждый раз, когда вы используете цикл foreach. Я рекомендую вам прочитать этот образец главы из C# в деталях Джона Скита.
Просто предупреждение: похоже, что это работает и будет работать большую часть времени, но однажды он внезапно начнет возвращать неверные данные без предупреждения. Может быть, новая версия компилятора, может быть новая языковая функция, может быть, новая оптимизация и т. д. Как сказал Эрик в другой ответ: «Цель стека вызовов - сообщить вам куда ты собираешься дальше, а не Откуда ты пришел».
Обратите внимание, что это будет ненадежно в коде выпуска из-за оптимизации. Кроме того, запуск приложения в режиме песочницы (сетевой ресурс) вообще не позволит вам захватить фрейм стека.
Рассмотрим аспектно-ориентированное программирование (AOP), например PostSharp, который вместо того, чтобы быть вызванным из вашего кода, изменяет ваш код и, таким образом, всегда знает, где он находится.
Вы абсолютно правы, что в выпуске это не сработает. Я не уверен, что мне нравится идея внедрения кода, но я предполагаю, что в некотором смысле оператор отладки требует модификации кода, но все же. Почему бы просто не вернуться к макросам C? Это хоть что-то видно.
Взгляните на Имя метода ведения журнала в .NET. Остерегайтесь использовать его в производственном коде. StackFrame может быть ненадежным ...
Краткое изложение содержания было бы неплохо.
В общем, вы можете использовать класс System.Diagnostics.StackTrace, чтобы получить System.Diagnostics.StackFrame, а затем использовать метод GetMethod(), чтобы получить объект System.Reflection.MethodBase. Однако для этого подхода есть некоторые предостережения:
(ПРИМЕЧАНИЕ: я просто расширяю ответ, предоставленный Фирасом Асадом..)
В режиме отладки с отключенной оптимизацией вы сможете увидеть, какой метод находится в трассировке стека?
@AttackingHobo: Да - если метод не встроен (оптимизация включен) или не является встроенным фреймом, вы его увидите.
Мы можем немного улучшить код Асада (текущий принятый ответ), создав экземпляр только того кадра, который нам действительно нужен, а не всего стека:
new StackFrame(1).GetMethod().Name;
Это могло бы работать немного лучше, хотя, по всей вероятности, все равно придется использовать полный стек для создания этого единственного кадра. Кроме того, он по-прежнему имеет те же предостережения, которые указал Алекс Лайман (оптимизатор / собственный код могут испортить результаты). Наконец, вы можете проверить, чтобы убедиться, что new StackFrame(1) или .GetFrame(1) не возвращают null, хотя такая возможность может показаться маловероятной.
См. Этот связанный вопрос: Можете ли вы использовать отражение, чтобы найти имя выполняемого в данный момент метода?
возможно ли, чтобы new ClassName(…) был равен нулю?
Что приятно, так это то, что это работает и в .NET Standard 2.0.
Может быть, вы ищете что-то вроде этого:
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
Другой подход, который я использовал, - это добавление параметра к рассматриваемому методу. Например, вместо void Foo() используйте void Foo(string context). Затем передайте некоторую уникальную строку, которая указывает контекст вызова.
Если вам нужен только вызывающий / контекст для разработки, вы можете удалить param перед отправкой.
/// <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". Поэтому, если вы вызываете этот метод в функции типа «журнал», вам нужно получить метод сразу после функции «журнала». поэтому вы должны вызвать GetCallingMethod ("log"). -Ваше здоровье
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 кадра» может легко вернуться и к вызовам методов.
В C# 5 эту информацию можно получить с помощью информация о вызывающем абоненте:
//using System.Runtime.CompilerServices;
public void SendError(string Message, [CallerMemberName] string callerName = "")
{
Console.WriteLine(callerName + "called me.");
}
Вы также можете получить [CallerFilePath] и [CallerLineNumber].
Здравствуйте, это не C# 5, он доступен в 4.5.
Версии @AFract Language (C#) не совпадают с версией .NET.
@stuartd Похоже, [CallerTypeName] был удален из текущей среды .Net (4.6.2) и Core CLR
@ Ph0en1x этого никогда не было во фреймворке, я хотел сказать, что было бы удобно, если бы это было, например как получить имя типа CallerMember
Есть ли у этого решения те же проблемы с оптимизацией компилятора, что и у решения StackTrace?
@DiegoDeberdt - я читал, что использование этого не имеет недостатков в отражении, так как он выполняет всю работу во время компиляции. Я считаю, что это верно в отношении того, что называется методом.
У меня это работает, даже если я использую StartCoroutine, IEnumerator в Unity. Спасибо.
Вы можете использовать информацию о вызывающем абоненте и дополнительные параметры:
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
Обратите внимание, что это не работает, если вызывающий абонент передал аргумент: CachingHelpers.WhoseThere("wrong name!"); ==> "wrong name!", потому что CallerMemberName заменяет только значение по умолчанию.
@ OlivierJacot-Descombes не работает таким образом, как не работал бы метод расширения, если бы вы передали ему параметр. вы могли бы использовать другой строковый параметр. Также обратите внимание, что resharper выдаст вам предупреждение, если вы попытаетесь передать аргумент, как вы это сделали.
@dove вы можете передать любой явный параметр this в метод расширения. Кроме того, Оливье прав, вы можете передать значение, а [CallerMemberName] не применяется; вместо этого он действует как переопределение там, где обычно используется значение по умолчанию. На самом деле, если мы посмотрим на IL, мы увидим, что результирующий метод ничем не отличается от того, который обычно был бы выдан для аргумента [opt], поэтому внедрение CallerMemberName является поведением среды CLR. Наконец, документы: «Атрибуты информации о вызывающем [...] влияет на значение по умолчанию, которое передается, когда аргумент опущен»
Это идеально и дружественно к async, с чем StackFrame вам не поможет. Также не влияет на вызов из лямбды.
Очевидно, это нельзя использовать с конечным объектом params, как в: GetMethodAndInitialParameterValues ([CallerMemberName] string memberName = null, params object [] parameterValues) {}
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. Лямбда-имена не генерируются случайным образом. Правило выглядит так:> <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);
var callingMethod = new StackFrame(1, true).GetMethod();
string source = callingMethod.ReflectedType.FullName + ": " + callingMethod.Name;
Я не голосовал против, но хотел отметить, что добавление текста, объясняющего, почему вы разместили очень похожую информацию (годы спустя), может повысить ценность вопроса и избежать дальнейшего голосования против.
Начиная с .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.
использованная литература
CallerFilePathAttributeCallerLineNumberAttributeCallerMemberNameAttributeКраткий обзор двух подходов, важная часть которых - сравнение скорости.
Определение вызывающей стороны во время компиляции
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
So you see, using the attributes is much, much faster! Nearly 25x faster in fact.
Этот метод кажется более подходящим. Он также работает в Xamarin без проблем с недоступностью пространств имен.
Очевидно, это поздний ответ, но у меня есть лучший вариант, если вы можете использовать .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");
Чтобы получить имя метода и имя класса, попробуйте следующее:
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 );
}
Дополнительная информация к ответу Фираса Ассаада.
Я использовал new StackFrame(1).GetMethod().Name; в .net core 2.1 с внедрением зависимостей, и я получаю метод вызова как «Пуск».
Пробовал с [System.Runtime.CompilerServices.CallerMemberName] string callerName = ""
и это дает мне правильный метод вызова
Если вы используете .net 4.5 beta +, вы можете использовать CallerInformation API.