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

202

Как и в заголовке: «Может ли отражение дать вам имя выполняемого в настоящее время метода».

Я склонен не догадываться из-за проблемы Гейзенберга. Как вы вызываете метод, который скажет вам текущий метод, не меняя, какой текущий метод? Но я надеюсь, что кто-то может доказать, что я ошибаюсь там.

Обновить:

  • Часть 2: Может ли это быть использовано для поиска в коде свойства?
  • Часть 3: Каким будет представление?

Окончательный результат
я узнал о MethodBase.GetCurrentMethod (). Я также узнал, что я могу не только создавать трассировку стека, но и создавать только тот кадр, который мне нужен, если я хочу.

Чтобы использовать это внутри свойства, просто возьмите .Substring (4), чтобы удалить 'set_' или 'get_'.

Джоэл Коухорн
источник
Джоэл, я знаю, это старый вопрос, но что ты имеешь в виду, создавая точную структуру метода?
Абхиджит
Это относится к конкретному элементу в стеке вызовов: той части трассировки стека, которая имеет значение.
Джоэл Кохорн

Ответы:

119

Начиная с .NET 4.5 вы также можете использовать [CallerMemberName]

Пример: установщик свойства (для ответа на часть 2):

    protected void SetProperty<T>(T value, [CallerMemberName] string property = null)
    {
        this.propertyValues[property] = value;
        OnPropertyChanged(property);
    }

    public string SomeProperty
    {
        set { SetProperty(value); }
    }

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

Джон Нильссон
источник
3
Это круто! Я использовал StackFrame(1)метод, описанный в других ответах, для регистрации, который, казалось, работал, пока Джиттер не решил начать встраивать вещи. Я не хотел добавлять атрибут для предотвращения встраивания по соображениям производительности. Использование [CallerMemberName]подхода решило проблему. Спасибо!
Брайан Роджерс
5
[CallerMemberName] доступен в сети 4 с
готовой сборкой
2
Учтите, что использование StackFrame (1) в режиме отладки должно работать. Но при использовании режима релиза при компиляции возможны некоторые оптимизации, и стек может оказаться не таким, как вы ожидаете.
Аксель О'Коннелл
Не вернет ли это вызывающий член (т.е. SomeProperty) вместо выполняемого в настоящее время метода?
Леннарт
1
Да, вызов сеттера приведет к вызову, OnPropertyChanged("SomeProperty")а не к немуOnPropertyChanged("SetProperty")
Джон Нильссон
189

Для не- asyncметодов можно использовать

System.Reflection.MethodBase.GetCurrentMethod().Name;

https://docs.microsoft.com/en-us/dotnet/api/system.reflection.methodbase.getcurrentmethod

Пожалуйста, помните, что для asyncметодов он вернет «MoveNext».

Эд Гинесс
источник
8
Имейте в виду, что это не всегда дает ожидаемые результаты. То есть, небольшие методы или свойства часто встроены в сборки выпуска, и в этом случае результатом будет имя метода вызывающей стороны.
Авель
5
Насколько я знаю, нет. Потому что во время выполнения MSIL больше не доступен из указателя выполнения (это JITted). Вы все еще можете использовать рефлексию, если знаете название метода. Дело в том, что, когда он встроен, выполняемый в настоящее время метод теперь является другим методом (т. Е. На один или несколько выше в стеке). Другими словами, метод исчез. Даже если вы пометите свой метод с помощью NoInlining, есть шанс, что он будет оптимизирован с помощью tail-call, и в этом случае он тоже исчезнет. Однако он будет работать в отладочной сборке.
Авель
1
Чтобы избежать встроенного, добавьте атрибут [MethodImpl (MethodImplOptions.NoInlining)] поверх метода.
alex.peter
Внутри asyncметода вы, скорее всего, получите «MoveNext» в качестве имени метода.
Виктор Ярема
46

Фрагмент, предоставленный Лексом, был немного длинным, поэтому я указываю на важную часть, поскольку никто другой не использовал точно такую ​​же технику:

string MethodName = new StackFrame(0).GetMethod().Name;

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

private string GetPropertyName()
{  //.SubString(4) strips the property prefix (get|set) from the name
    return new StackFrame(1).GetMethod().Name.Substring(4);
}

Это тоже одна строка;)

Джоэл Коухорн
источник
может быть публичной статической строкой GetPropertyName () во вспомогательном классе? статический метод?
Kiquenet
2
То же, что и с ответом Эда Гиннесса: стек может быть другим в сборках релиза, и первый метод может не совпадать с текущим методом в случаях встраивания или оптимизации хвостового вызова.
Авель
См. Ответ Джона Нильссона, чтобы узнать, как обойти эту проблему, если вы используете .Net 4.5.
Брайан Роджерс
это может быть лучше, чем принятый ответ и выше ответ тоже
T.Todua
16

Попробуйте это внутри метода Main в пустой консольной программе:

MethodBase method = MethodBase.GetCurrentMethod();
Console.WriteLine(method.Name);

Консольный вывод:
Main

Ларс Мухлум
источник
12

Определенно да.

Если вы хотите, чтобы объект манипулировал, я на самом деле использую такую ​​функцию:

public static T CreateWrapper<T>(Exception innerException, params object[] parameterValues) where T : Exception, new()
{
    if (parameterValues == null)
    {
        parameterValues = new object[0];
    }

    Exception exception   = null;
    StringBuilder builder = new StringBuilder();
    MethodBase method     = new StackFrame(2).GetMethod();
    ParameterInfo[] parameters = method.GetParameters();
    builder.AppendFormat(CultureInfo.InvariantCulture, ExceptionFormat, new object[] { method.DeclaringType.Name, method.Name });
    if ((parameters.Length > 0) || (parameterValues.Length > 0))
    {
        builder.Append(GetParameterList(parameters, parameterValues));
    }

    exception = (Exception)Activator.CreateInstance(typeof(T), new object[] { builder.ToString(), innerException });
    return (T)exception;
}

Эта строка:

MethodBase method     = new StackFrame(2).GetMethod();

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

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

MethodBase.GetCurrentMethod()

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

После 4.5 вы можете теперь использовать [CallerMemberNameAttribute] как часть параметров метода, чтобы получить строку с именем метода - это может помочь в некоторых сценариях (но на самом деле, скажем, в примере выше)

public void Foo ([CallerMemberName] string methodName = null)

Похоже, что это в основном решение для поддержки INotifyPropertyChanged, где ранее у вас были строки, засоренные на протяжении всего кода события.

закон
источник
Не глупо; Я просто передал их. Возможно, вы могли бы сделать что-нибудь, чтобы было проще взглянуть на них, но соотношение усилия к вознаграждению, казалось, способствовало тому, чтобы оно было простым. По сути, dev просто копирует в список параметров сигнатуру метода (конечно, удаляя типы).
Lex
что это такое: ExceptionFormat и GetParameterList?
Kiquenet
Позднее в ответе, но: ExceptionFormat - это постоянный формат строки, а GetParameterList - простая функция, которая форматирует параметры со значениями (вы можете сделать это встроенным)
Lex
11

Сравнение способов получения имени метода - используя произвольную временную конструкцию в LinqPad:

КОД

void Main()
{
    // from http://blogs.msdn.com/b/webdevelopertips/archive/2009/06/23/tip-83-did-you-know-you-can-get-the-name-of-the-calling-method-from-the-stack-using-reflection.aspx
    // and /programming/2652460/c-sharp-how-to-get-the-name-of-the-current-method-from-code

    var fn = new methods();

    fn.reflection().Dump("reflection");
    fn.stacktrace().Dump("stacktrace");
    fn.inlineconstant().Dump("inlineconstant");
    fn.constant().Dump("constant");
    fn.expr().Dump("expr");
    fn.exprmember().Dump("exprmember");
    fn.callermember().Dump("callermember");

    new Perf {
        { "reflection", n => fn.reflection() },
        { "stacktrace", n => fn.stacktrace() },
        { "inlineconstant", n => fn.inlineconstant() },
        { "constant", n => fn.constant() },
        { "expr", n => fn.expr() },
        { "exprmember", n => fn.exprmember() },
        { "callermember", n => fn.callermember() },
    }.Vs("Method name retrieval");
}

// Define other methods and classes here
class methods {
    public string reflection() {
        return System.Reflection.MethodBase.GetCurrentMethod().Name;
    }
    public string stacktrace() {
        return new StackTrace().GetFrame(0).GetMethod().Name;
    }
    public string inlineconstant() {
        return "inlineconstant";
    }
    const string CONSTANT_NAME = "constant";
    public string constant() {
        return CONSTANT_NAME;
    }
    public string expr() {
        Expression<Func<methods, string>> ex = e => e.expr();
        return ex.ToString();
    }
    public string exprmember() {
        return expressionName<methods,string>(e => e.exprmember);
    }
    protected string expressionName<T,P>(Expression<Func<T,Func<P>>> action) {
        // https://stackoverflow.com/a/9015598/1037948
        return ((((action.Body as UnaryExpression).Operand as MethodCallExpression).Object as ConstantExpression).Value as MethodInfo).Name;
    }
    public string callermember([CallerMemberName]string name = null) {
        return name;
    }
}

ПОЛУЧЕННЫЕ РЕЗУЛЬТАТЫ

отражение отражение

стек трассировки стек трассировки

inlineconstant inlineconstant

постоянная константа

expr e => e.expr ()

exprmember exprmember

callermember Главная

Method name retrieval: (reflection) vs (stacktrace) vs (inlineconstant) vs (constant) vs (expr) vs (exprmember) vs (callermember) 

 154673 ticks elapsed ( 15.4673 ms) - reflection
2588601 ticks elapsed (258.8601 ms) - stacktrace
   1985 ticks elapsed (  0.1985 ms) - inlineconstant
   1385 ticks elapsed (  0.1385 ms) - constant
1366706 ticks elapsed (136.6706 ms) - expr
 775160 ticks elapsed ( 77.516  ms) - exprmember
   2073 ticks elapsed (  0.2073 ms) - callermember


>> winner: constant

Обратите внимание , что exprи callermemberметоды не совсем «правильно». И там вы видите повторение связанного комментария о том, что отражение в ~ 15 раз быстрее, чем трассировка стека.

drzaus
источник
9

РЕДАКТИРОВАТЬ: MethodBase, вероятно, лучший способ просто получить метод, в котором вы находитесь (в отличие от всего стека вызовов). Я все еще был бы обеспокоен по поводу встраивания однако.

Вы можете использовать StackTrace в методе:

StackTrace st = new StackTrace(true);

И посмотрите на кадры:

// The first frame will be the method you want (However, see caution below)
st.GetFrames();

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

[MethodImpl(MethodImplOptions.NoInlining)]
Денис Филлипс
источник
Инлайн из-за оптимизации Release особенно сложен, так как код будет вести себя по-разному в конфигурациях Debug и Release. Остерегайтесь небольших объектов недвижимости, они являются наиболее вероятными жертвами этого.
ДК.
Интересно, почему Woud вы используете new StackTrace(true)вместо new StackTrace(false). Установка этого параметра trueприведет к тому, что трассировка стека попытается захватить имя файла, номер строки и т. Д., Что может замедлить этот вызов. Иначе хороший ответ
Ивайло Славов
6

Простой способ разобраться:

System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.FullName + "." + System.Reflection.MethodBase.GetCurrentMethod().Name;

Если System.Reflection включен в блок using:

MethodBase.GetCurrentMethod().DeclaringType.FullName + "." + MethodBase.GetCurrentMethod().Name;
Акула
источник
4

Как насчет этого:

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
jesal
источник
0

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

    /// <summary>
    /// Return the full name of method
    /// </summary>
    /// <param name="obj">Class that calls this method (use Report(this))</param>
    /// <returns></returns>
    public string Report(object obj)
    {
        var reflectedType = new StackTrace().GetFrame(1).GetMethod().ReflectedType;
        if (reflectedType == null) return null;

        var i = reflectedType.FullName;
        var ii = new StackTrace().GetFrame(1).GetMethod().Name;

        return string.Concat(i, ".", ii);
    }
Адриано Сильва Рибейро
источник
0

Я просто сделал это с помощью простого статического класса:

using System.Runtime.CompilerServices;
.
.
.
    public static class MyMethodName
        {
            public static string Show([CallerMemberName] string name = "")
            {
                return name;
            }
        }

тогда в вашем коде:

private void button1_Click(object sender, EventArgs e)
        {
            textBox1.Text = MyMethodName.Show();
        }

        private void button2_Click(object sender, EventArgs e)
        {
            textBox1.Text = MyMethodName.Show();
        }
Майкл Косак
источник
-1
new StackTrace().ToString().Split("\r\n",StringSplitOptions.RemoveEmptyEntries)[0].Replace("at ","").Trim()
Баглай Вячеслав
источник