Czy możesz użyć refleksji, aby znaleźć nazwę aktualnie wykonywanej metody?


202

Jak mówi tytuł: Czy odbicie może dać nazwę aktualnie wykonywanej metody.

Nie jestem skłonny zgadywać z powodu problemu Heisenberga. Jak wywołać metodę, która poinformuje cię o bieżącej metodzie bez zmiany jej aktualnej metody? Ale mam nadzieję, że ktoś może udowodnić, że się mylę.

Aktualizacja:

  • Część 2: Czy można tego również użyć do przeszukiwania kodu wewnątrz właściwości?
  • Część 3: Jak wyglądałby występ?

Ostateczny wynik
Dowiedziałem się o MethodBase.GetCurrentMethod (). Dowiedziałem się również, że nie tylko mogę utworzyć ślad stosu, ale mogę także stworzyć dokładnie tę klatkę, której potrzebuję, jeśli chcę.

Aby użyć tego wewnątrz właściwości, wystarczy wziąć .Substring (4), aby usunąć „set_” lub „get_”.


Joel, wiem, że to stare pytanie, ale co masz na myśli, tworząc dokładną ramkę metody?
Abhijeet

Odnosi się do określonego elementu w stosie wywołań: części śladu stosu, która ma znaczenie.
Joel Coehoorn

Odpowiedzi:


119

Począwszy od .NET 4.5 możesz także używać [CallerMemberName]

Przykład: ustawiacz właściwości (w odpowiedzi na część 2):

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

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

Kompilator dostarczy pasujące literały łańcuchowe w miejscach wywołań, więc w zasadzie nie ma narzutu wydajności.


3
To jest świetne! Używałem StackFrame(1)metody opisanej w innych odpowiedziach do rejestrowania, która wydawała się działać, dopóki Jitter nie zdecydował się na wprowadzenie funkcji inline. Nie chciałem dodawać atrybutu, aby zapobiec wstawianiu ze względu na wydajność. Zastosowanie [CallerMemberName]podejścia rozwiązało problem. Dzięki!
Brian Rogers

5
[CallerMemberName] jest dostępny w sieci 4 z kompilacją BCL
Venson

2
Weź pod uwagę, że używanie StackFrame (1) w trybie debugowania powinno działać. Ale podczas korzystania z trybu wydania podczas kompilacji mogą wystąpić pewne optymalizacje, a stos może nie być zgodny z oczekiwaniami.
Axel O'Connell

Czy to nie zwróci wywołującego członka (tj. SomeProperty), zamiast obecnie wykonywanej metody?
Lennart,

1
Tak, przywołanie setera spowoduje wywołanie OnPropertyChanged("SomeProperty")i nieOnPropertyChanged("SetProperty")
John Nilsson

189

W przypadku asyncmetod innych niż metody można użyć

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

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

Pamiętaj, że dla asyncmetod zwróci „MoveNext”.


8
Pamiętaj, że nie zawsze przynosi to oczekiwane rezultaty. Tzn. Małe metody lub właściwości są często wbudowane w kompilacje wersji, w którym to przypadku wynikiem będzie nazwa metody wywołującej.
Abel

5
O ile mi wiadomo, nie. Ponieważ w czasie wykonywania MSIL nie jest już dostępny ze wskaźnika wykonania (jest JITted). Nadal możesz używać odbicia, jeśli znasz nazwę metody. Chodzi o to, że po wstawieniu aktualnie wykonywana metoda jest teraz inną metodą (tj. O jeden lub więcej wyżej na stosie). Innymi słowy, metoda zniknęła. Nawet jeśli zaznaczysz swoją metodę za pomocą NoInlining, nadal istnieje szansa, że ​​zostanie zoptymalizowana funkcja tail-call, w którym to przypadku również jej nie ma. Działa to jednak podczas kompilacji debugowania.
Abel

1
Aby uniknąć wbudowanego dodawania atrybutu [MethodImpl (MethodImplOptions.NoInlining)] na górze metody.
alex.peter

Wewnątrz asyncmetody najprawdopodobniej otrzymasz „MoveNext” jako nazwę metody.
Victor Yarema

46

Fragment dostarczony przez Lexa był trochę długi, więc wskazuję na ważną część, ponieważ nikt inny nie zastosował dokładnie tej samej techniki:

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

Powinno to zwrócić identyczne wyniki do metody MethodBase.GetCurrentMethod (). Nazwa techniki, ale nadal warto ją wskazać, ponieważ mogłem ją zaimplementować raz we własnej metodzie przy użyciu indeksu 1 dla poprzedniej metody i wywołać ją z wielu różnych właściwości. Ponadto zwraca tylko jedną ramkę, a nie cały ślad stosu:

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

To też jedna linijka;)


może być publicznym ciągiem statycznym GetPropertyName () w klasie pomocniczej? metoda statyczna?
Kiquenet,

2
To samo, co odpowiedź Eda Guinessa: stos może się różnić w kompilacjach wersji, a pierwsza metoda może nie być taka sama jak obecna metoda w przypadkach inlinizacji lub optymalizacji wywołania ogona.
Abel

Zobacz odpowiedź Johna Nilssona na dobry sposób na obejście problemu wstawiania, jeśli używasz .Net 4.5.
Brian Rogers

może to być lepsza odpowiedź niż zaakceptowana, a także odpowiedź powyżej
T.Todua

16

Wypróbuj to w metodzie Main w pustym programie konsoli:

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

Dane wyjściowe konsoli:
Main


12

Tak, zdecydowanie.

Jeśli chcesz manipulować obiektem, używam funkcji takiej jak ta:

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

Ta linia:

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

Podchodzi do ramki stosu, aby znaleźć metodę wywołującą, a następnie używamy refleksji, aby uzyskać wartości informacji o parametrach przekazane do niej dla ogólnej funkcji raportowania błędów. Aby uzyskać bieżącą metodę, po prostu użyj zamiast tego bieżącej ramki stosu (1).

Jak powiedzieli inni dla obecnej nazwy metod, możesz także użyć:

MethodBase.GetCurrentMethod()

Wolę chodzić po stosie, ponieważ jeśli spojrzysz wewnętrznie na tę metodę, po prostu tworzy StackCrawlMark. Bezpośrednie adresowanie stosu wydaje mi się wyraźniejsze

Po wersji 4.5 możesz teraz użyć [CallerMemberNameAttribute] jako części parametrów metody, aby uzyskać ciąg nazwy metody - może to pomóc w niektórych scenariuszach (ale tak naprawdę w powyższym przykładzie)

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

Wydawało się, że jest to głównie rozwiązanie dla obsługi INotifyPropertyChanged, gdzie wcześniej ciągi znaków były zaśmiecone przez cały kod zdarzenia.


Nie głupie; Po prostu je przekazałem. Prawdopodobnie mógłbyś zrobić coś, co uprościłoby spojrzenie, ale stosunek wysiłku do nagrody wydawał się sprzyjać utrzymaniu prostoty. Zasadniczo dev po prostu kopiuje listę parametrów sygnatury metody (oczywiście usuwając typy).
Lex

co to jest: ExceptionFormat i GetParameterList?
Kiquenet,

Dość późno w odpowiedzi, ale: ExceptionFormat jest stałym formatem ciągów, a GetParameterList to po prostu funkcja, która formatuje parametry za pomocą wartości (można to zrobić inline)
Lex

11

Porównywanie sposobów uzyskania nazwy metody - przy użyciu dowolnej konstrukcji czasowej w LinqPad:

KOD

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

WYNIKI

odbicie odbicie

stacktrace stacktrace

inlineconant inlineconstant

stała stała

wyrażenie e => e.expr ()

exprmember exprmember

Callermember Main

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

Zauważ, że metody expri callermembernie są całkiem „właściwe”. I tam pojawia się powtórzenie powiązanego komentarza, że odbicie jest ~ 15 razy szybsze niż stacktrace.


9

EDYCJA: MethodBase jest prawdopodobnie lepszym sposobem na uzyskanie metody, w której się znajdujesz (w przeciwieństwie do całego stosu wywołań). Nadal jednak martwię się o inline.

Możesz użyć StackTrace w ramach metody:

StackTrace st = new StackTrace(true);

I spojrzenie na ramki:

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

Należy jednak pamiętać, że jeśli metoda jest wbudowana, nie będziesz w metodzie, którą uważasz za swoją. Możesz użyć atrybutu, aby zapobiec wstawianiu:

[MethodImpl(MethodImplOptions.NoInlining)]

Inline ze względu na optymalizację wydania jest szczególnie trudny, ponieważ kod będzie zachowywać się inaczej w konfiguracjach debugowania i wydania. Uważaj na małe nieruchomości, są to najbardziej prawdopodobne ofiary tego.
DK.

Zastanawiam się, dlaczego woud używasz new StackTrace(true)zamiast new StackTrace(false). Ustawienie tej wartości truespowoduje, że ślad stosu podejmie próbę przechwycenia nazwy pliku, numeru linii itp., Co może spowolnić to wywołanie. W przeciwnym razie ładna odpowiedź
Ivaylo Slavov

6

Najprostszym sposobem postępowania jest:

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

Jeśli element System.Reflection jest zawarty w bloku używającym:

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

4

Co powiesz na to:

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


0

Spróbuj tego...

    /// <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

Właśnie zrobiłem to z prostą klasą statyczną:

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

następnie w kodzie:

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()
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.