Können Sie mithilfe von Reflection den Namen der aktuell ausgeführten Methode ermitteln?

202

Wie der Titel schon sagt: Kann Reflection Ihnen den Namen der aktuell ausgeführten Methode geben?

Ich neige dazu, wegen des Heisenberg-Problems nicht zu raten. Wie rufen Sie eine Methode auf, die Ihnen die aktuelle Methode anzeigt, ohne die aktuelle Methode zu ändern? Aber ich hoffe, jemand kann mir dort das Gegenteil beweisen.

Aktualisieren:

  • Teil 2: Könnte dies verwendet werden, um auch im Code nach einer Eigenschaft zu suchen?
  • Teil 3: Wie würde die Aufführung aussehen?

Endergebnis
Ich habe von MethodBase.GetCurrentMethod () erfahren. Ich habe auch gelernt, dass ich nicht nur einen Stack-Trace erstellen kann, sondern nur den genauen Frame erstellen kann, den ich benötige, wenn ich möchte.

Um dies in einer Eigenschaft zu verwenden, nehmen Sie einfach eine .Substring (4), um 'set_' oder 'get_' zu entfernen.

Joel Coehoorn
quelle
Joel, ich weiß, es ist eine alte Frage, aber was meinst du damit, einen genauen Rahmen einer Methode zu erstellen?
Abhijeet
Es bezieht sich auf ein bestimmtes Element im Aufrufstapel: den Teil der Stapelverfolgung, der wichtig ist.
Joel Coehoorn

Antworten:

119

Ab .NET 4.5 können Sie auch [CallerMemberName] verwenden.

Beispiel: ein Eigenschaftssetzer (um Teil 2 zu beantworten):

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

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

Der Compiler stellt auf Callsites übereinstimmende String-Literale bereit, sodass im Grunde kein Leistungsaufwand entsteht.

John Nilsson
quelle
3
Das ist toll! Ich verwendete die StackFrame(1)in anderen Antworten beschriebene Methode für die Protokollierung, die zu funktionieren schien, bis der Jitter beschloss, mit dem Inlining zu beginnen. Ich wollte das Attribut nicht hinzufügen, um Inlining aus Leistungsgründen zu verhindern. Mit dem [CallerMemberName]Ansatz wurde das Problem behoben. Vielen Dank!
Brian Rogers
5
[CallerMemberName] ist verfügbar bei net 4 mit dem gepackten BCL-Build
Venson
2
Beachten Sie, dass die Verwendung von StackFrame (1) im Debug-Modus funktionieren sollte. Wenn Sie beim Kompilieren den Release-Modus verwenden, gibt es möglicherweise einige Optimierungen, und der Stack entspricht möglicherweise nicht Ihren Erwartungen.
Axel O'Connell
Gibt dies nicht das aufrufende Mitglied (dh SomeProperty) anstelle der aktuell ausgeführten Methode zurück?
Lennart
1
Ja, das Aufrufen des Setters führt zu einem Anruf bei OnPropertyChanged("SomeProperty")und nichtOnPropertyChanged("SetProperty")
John Nilsson
189

Für Nicht- asyncMethoden kann man verwenden

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

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

Bitte denken Sie daran, dass für asyncMethoden "MoveNext" zurückgegeben wird.

Ed Guiness
quelle
8
Beachten Sie, dass dies nicht immer zu den erwarteten Ergebnissen führt. Das heißt, kleine Methoden oder Eigenschaften werden häufig in Release-Builds eingefügt. In diesem Fall ist das Ergebnis stattdessen der Methodenname des Aufrufers.
Abel
5
Soweit ich weiß, nein. Da zur Laufzeit die MSIL nicht mehr über den Ausführungszeiger verfügbar ist (sie ist JITted). Sie können Reflection weiterhin verwenden, wenn Sie den Namen der Methode kennen. Der Punkt ist, wenn inline, dass die aktuell ausgeführte Methode jetzt eine andere Methode ist (dh eine oder mehrere höher im Stapel). Mit anderen Worten, die Methode verschwand. Selbst wenn Sie Ihre Methode mit NoInlining markieren, besteht immer noch die Möglichkeit, dass sie für den Tail-Call optimiert wird. In diesem Fall ist sie auch weg. Es wird jedoch während des Debug-Builds funktionieren.
Abel
1
Um Inline zu vermeiden, fügen Sie das Attribut [MethodImpl (MethodImplOptions.NoInlining)] über der Methode hinzu.
Alex.peter
Innerhalb der asyncMethode erhalten Sie höchstwahrscheinlich "MoveNext" als Methodennamen.
Victor Yarema
46

Das von Lex bereitgestellte Snippet war etwas lang, daher weise ich auf den wichtigen Teil hin, da sonst niemand genau dieselbe Technik verwendete:

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

Dies sollte identische Ergebnisse für die MethodBase.GetCurrentMethod (). Name- Technik zurückgeben, aber es lohnt sich immer noch darauf hinzuweisen, da ich dies einmal in einer eigenen Methode implementieren könnte, indem ich Index 1 für die vorherige Methode verwende und es aus einer Reihe verschiedener Eigenschaften aufrufe. Außerdem wird nur ein Frame und nicht der gesamte Stack-Trace zurückgegeben:

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

Es ist auch ein Einzeiler;)

Joel Coehoorn
quelle
kann eine öffentliche statische Zeichenfolge GetPropertyName () in einer Hilfsklasse sein? statische Methode?
Kiquenet
2
Wie bei der Antwort von Ed Guiness: Der Stack kann in Release-Builds unterschiedlich sein, und die erste Methode ist möglicherweise nicht dieselbe wie die aktuelle Methode bei Inlining- oder Tail-Call-Optimierung.
Abel
In der Antwort von John Nilsson finden Sie eine gute Möglichkeit, das Inlining-Problem zu umgehen, wenn Sie .Net 4.5 verwenden.
Brian Rogers
Dies könnte besser sein als akzeptierte Antwort und über Antwort auch
T.Todua
16

Versuchen Sie dies innerhalb der Main-Methode in einem leeren Konsolenprogramm:

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

Konsolenausgabe:
Main

Lars Mæhlum
quelle
12

Ja definitiv.

Wenn Sie möchten, dass ein Objekt manipuliert wird, verwende ich tatsächlich eine Funktion wie die folgende:

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

Diese Linie:

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

Geht den Stapelrahmen hoch, um die aufrufende Methode zu finden, und verwendet dann Reflection, um Parameterinformationswerte zu erhalten, die für eine generische Fehlerberichterstattungsfunktion an ihn übergeben wurden. Um die aktuelle Methode zu erhalten, verwenden Sie stattdessen einfach den aktuellen Stapelrahmen (1).

Wie andere für den aktuellen Methodennamen gesagt haben, können Sie auch Folgendes verwenden:

MethodBase.GetCurrentMethod()

Ich gehe lieber über den Stapel, weil bei interner Betrachtung dieser Methode ohnehin einfach ein StackCrawlMark erstellt wird. Die direkte Adressierung des Stapels erscheint mir klarer

Nach 4.5 können Sie jetzt das [CallerMemberNameAttribute] als Teil der Methodenparameter verwenden, um eine Zeichenfolge des Methodennamens abzurufen. Dies kann in einigen Szenarien hilfreich sein (im obigen Beispiel jedoch wirklich).

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

Dies schien hauptsächlich eine Lösung für die INotifyPropertyChanged-Unterstützung zu sein, bei der zuvor Zeichenfolgen im gesamten Ereigniscode verstreut waren.

Lex
quelle
Nicht dumm; Ich habe sie einfach weitergegeben. Sie könnten wahrscheinlich etwas tun, um das Betrachten einfacher zu gestalten, aber das Verhältnis von Aufwand zu Belohnung schien es zu begünstigen, es einfach zu halten. Im Wesentlichen kopiert der Entwickler nur in die Parameterliste der Methodensignatur (wobei natürlich Typen entfernt werden).
Lex
Was ist das: ExceptionFormat und GetParameterList?
Kiquenet
Viel zu spät in der Antwort, aber: ExceptionFormat ist ein konstantes Zeichenfolgenformat und GetParameterList ist eine einfache Funktion, die die Parameter mit den Werten formatiert (Sie könnten dies inline tun)
Lex
11

Vergleichen von Möglichkeiten zum Abrufen des Methodennamens - Verwenden eines beliebigen Timing-Konstrukts in LinqPad:

CODE

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

ERGEBNISSE

Reflexion Reflexion

Stacktrace Stacktrace

inlineconstant inlineconstant

Konstante Konstante

expr 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

Beachten Sie, dass die Methoden exprund callermembernicht ganz "richtig" sind. Und dort sehen Sie eine Wiederholung eines verwandten Kommentars, dass die Reflexion ~ 15x schneller ist als die Stapelverfolgung.

drzaus
quelle
9

BEARBEITEN: MethodBase ist wahrscheinlich ein besserer Weg, um nur die Methode zu erhalten, in der Sie sich befinden (im Gegensatz zum gesamten aufrufenden Stack). Ich würde mir jedoch immer noch Sorgen um Inlining machen.

Sie können eine StackTrace innerhalb der Methode verwenden:

StackTrace st = new StackTrace(true);

Und der Blick auf die Rahmen:

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

Beachten Sie jedoch, dass Sie sich nicht in der Methode befinden, von der Sie glauben, dass Sie sie sind, wenn die Methode inline ist. Sie können ein Attribut verwenden, um Inlining zu verhindern:

[MethodImpl(MethodImplOptions.NoInlining)]
Denis Phillips
quelle
Inline aufgrund der Release-Optimierung ist besonders schwierig, da sich der Code in Debug- und Release-Konfigurationen unterschiedlich verhält. Achten Sie auf kleine Immobilien, sie sind die wahrscheinlichsten Opfer davon.
DK.
Ich frage mich, warum Sie new StackTrace(true)stattdessen verwenden würden new StackTrace(false). Wenn Sie dies trueauf setzen, versucht der Stack-Trace, den Dateinamen, die Zeilennummer usw. zu erfassen, wodurch dieser Aufruf möglicherweise langsamer wird. Ansonsten eine schöne Antwort
Ivaylo Slavov
6

Der einfache Weg ist:

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

Wenn die System.Reflection im using-Block enthalten ist:

MethodBase.GetCurrentMethod().DeclaringType.FullName + "." + MethodBase.GetCurrentMethod().Name;
Hai
quelle
4

Wie wäre es damit:

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
Jesus
quelle
0

Versuche dies...

    /// <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);
    }
Adriano silva ribeiro
quelle
0

Ich habe das gerade mit einer einfachen statischen Klasse gemacht:

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

dann in deinem Code:

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

        private void button2_Click(object sender, EventArgs e)
        {
            textBox1.Text = MyMethodName.Show();
        }
Michael Kosak
quelle
-1
new StackTrace().ToString().Split("\r\n",StringSplitOptions.RemoveEmptyEntries)[0].Replace("at ","").Trim()
Baglay Vyacheslav
quelle