Wie finde ich die Methode, die die aktuelle Methode aufgerufen hat?

503

Wie kann ich beim Anmelden in C # den Namen der Methode erfahren, die die aktuelle Methode aufgerufen hat? Ich weiß alles über System.Reflection.MethodBase.GetCurrentMethod(), aber ich möchte in der Stapelverfolgung einen Schritt darunter gehen. Ich habe überlegt, den Stack-Trace zu analysieren, hoffe aber, einen saubereren, expliziteren Weg zu finden, etwa Assembly.GetCallingAssembly()für Methoden.

Flipdoubt
quelle
22
Wenn Sie .net 4.5 Beta + verwenden, können Sie die CallerInformation-API verwenden .
Rohit Sharma
5
Anruferinformationen sind auch viel schneller
Taube
4
Ich habe eine schnelle BenchmarkDotNet Benchmark der drei wichtigsten Methoden ( StackTrace, StackFrameund CallerMemberName) und veröffentlicht die Ergebnisse als Kern für die anderen hier zu sehen: gist.github.com/wilson0x4d/7b30c3913e74adf4ad99b09163a57a1f
Shaun Wilson

Antworten:

512

Versuche dies:

using System.Diagnostics;
// Get call stack
StackTrace stackTrace = new StackTrace(); 
// Get calling method name
Console.WriteLine(stackTrace.GetFrame(1).GetMethod().Name);

Einzeiler:

(new System.Diagnostics.StackTrace()).GetFrame(1).GetMethod().Name

Es ist von Get Calling Method using Reflection [C #] .

Firas Assaad
quelle
12
Sie können auch nur den Rahmen erstellen, den Sie benötigen, und nicht den gesamten Stapel:
Joel Coehoorn
187
neuer StackFrame (1) .GetMethod (). Name;
Joel Coehoorn
12
Dies ist jedoch nicht ganz zuverlässig. Mal sehen, ob das in einem Kommentar funktioniert! Versuchen Sie Folgendes in einer Konsolenanwendung, und Sie sehen, dass Compiler-Optimierungen dies verhindern. statische Leere Main (string [] args) {CallIt (); } private static void CallIt () {Final (); } static void Final () {StackTrace trace = new StackTrace (); StackFrame frame = trace.GetFrame (1); Console.WriteLine ("{0}. {1} ()", frame.GetMethod (). DeclaringType.FullName, frame.GetMethod (). Name); }
BlackWasp
10
Dies funktioniert nicht, wenn der Compiler Inlines oder Tail-Call die Methode optimiert. In diesem Fall wird der Stack reduziert und Sie finden andere Werte als erwartet. Wenn Sie dies nur in Debug-Builds verwenden, funktioniert es jedoch gut.
Abel
46
In der Vergangenheit habe ich das Compiler-Attribut [MethodImplAttribute (MethodImplOptions.NoInlining)] vor der Methode hinzugefügt , mit der der Stack-Trace nachgeschlagen wird. Dies stellt sicher, dass der Compiler die Methode nicht
Jordan Rieger
363

In C # 5 können Sie diese Informationen mithilfe der Anruferinformationen abrufen :

//using System.Runtime.CompilerServices;
public void SendError(string Message, [CallerMemberName] string callerName = "") 
{ 
    Console.WriteLine(callerName + "called me."); 
} 

Sie können auch das [CallerFilePath]und bekommen [CallerLineNumber].

Münze
quelle
13
Hallo, es ist nicht C # 5, es ist in 4.5 verfügbar.
Nach dem
35
@AFract Language (C #) -Versionen sind nicht mit der .NET-Version identisch.
Kwesolowski
6
@stuartd Sieht aus wie [CallerTypeName]aus dem aktuellen .Net Framework (4.6.2) und Core CLR
Ph0en1x
4
@ Ph0en1x es war nie im Framework, mein Punkt war, es wäre praktisch, wenn es wäre, z. B. wie man den Typnamen eines CallerMember erhält
stuartd
3
@DiegoDeberdt - Ich habe gelesen, dass die Verwendung dieser Funktion keine Nachteile hat, da sie die gesamte Arbeit zur Kompilierungszeit erledigt. Ich glaube, es ist richtig, was die Methode genannt wird.
Cchamberlain
109

Sie können Anruferinformationen und optionale Parameter verwenden:

public static string WhoseThere([CallerMemberName] string memberName = "")
{
       return memberName;
}

Dieser Test veranschaulicht dies:

[Test]
public void Should_get_name_of_calling_method()
{
    var methodName = CachingHelpers.WhoseThere();
    Assert.That(methodName, Is.EqualTo("Should_get_name_of_calling_method"));
}

Während der StackTrace oben ziemlich schnell arbeitet und in den meisten Fällen kein Leistungsproblem darstellt, sind die Anruferinformationen noch viel schneller. In einem Beispiel von 1000 Iterationen habe ich es 40-mal schneller getaktet.

Taube
quelle
Nur verfügbar ab .Net 4.5
DerApe
1
Beachten Sie, dass dies nicht funktioniert, wenn der Aufrufer ein Agrument übergibt: CachingHelpers.WhoseThere("wrong name!");==>, "wrong name!"da das CallerMemberNamenur den Standardwert ersetzt.
Olivier Jacot-Descombes
@ OlivierJacot-Descombes funktioniert nicht auf die gleiche Weise wie eine Erweiterungsmethode, wenn Sie einen Parameter an sie übergeben würden. Sie könnten aber einen anderen String-Parameter verwenden, der verwendet werden könnte. Beachten Sie auch, dass Resharper Sie warnen würde, wenn Sie versuchen würden, ein Argument wie Sie zu übergeben.
Taube
1
@dove Sie können jeden expliziten thisParameter an eine Erweiterungsmethode übergeben. Auch Olivier ist richtig, Sie können einen Wert übergeben und [CallerMemberName]wird nicht angewendet; Stattdessen fungiert es als Überschreibung, bei der normalerweise der Standardwert verwendet wird. Wenn wir uns die IL ansehen, können wir tatsächlich sehen, dass die resultierende Methode nicht anders ist als die, die normalerweise für ein [opt]Arg ausgegeben worden wäre. Die Injektion von CallerMemberNameist daher ein CLR-Verhalten. Schließlich die Dokumente: "Die Anruferinfo-Attribute [...] wirken sich auf den Standardwert aus, der übergeben wird, wenn das Argument weggelassen wird "
Shaun Wilson
2
Dies ist perfekt und asyncfreundlich, was StackFrameIhnen nicht helfen wird. Hat auch keinen Einfluss darauf, von einem Lambda angerufen zu werden.
Aaron
65

Eine kurze Zusammenfassung der beiden Ansätze, wobei der Geschwindigkeitsvergleich der wichtige Teil ist.

http://geekswithblogs.net/BlackRabbitCoder/archive/2013/07/25/c.net-little-wonders-getting-caller-information.aspx

Ermitteln des Aufrufers zur Kompilierungszeit

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

Ermitteln des Anrufers anhand des Stapels

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

Vergleich der beiden Ansätze

Time for 1,000,000 iterations with Attributes: 196 ms
Time for 1,000,000 iterations with StackTrace: 5096 ms

Sie sehen also, die Verwendung der Attribute ist viel, viel schneller! Fast 25x schneller.

Tikall
quelle
Diese Methode scheint überlegen zu sein. Es funktioniert auch in Xamarin, ohne dass Namespaces verfügbar sind.
Lyndon Hughey
63

Wir können den Code von Herrn Assad (die derzeit akzeptierte Antwort) ein wenig verbessern, indem wir nur den Frame instanziieren, den wir tatsächlich benötigen, und nicht den gesamten Stapel:

new StackFrame(1).GetMethod().Name;

Dies könnte etwas besser funktionieren, obwohl es aller Wahrscheinlichkeit nach immer noch den vollen Stapel verwenden muss, um diesen einzelnen Frame zu erstellen. Außerdem weist es immer noch die gleichen Einschränkungen auf, auf die Alex Lyman hingewiesen hat (Optimierer / nativer Code können die Ergebnisse verfälschen). Schließlich möchten Sie vielleicht überprüfen, ob Sie zurückkehren new StackFrame(1)oder .GetFrame(1)nicht null, so unwahrscheinlich diese Möglichkeit auch sein mag.

Siehe diese verwandte Frage: Können Sie mithilfe von Reflection den Namen der aktuell ausgeführten Methode ermitteln?

Joel Coehoorn
quelle
1
ist es überhaupt möglich, dass new ClassName(…)gleich null ist?
Anzeigename
1
Schön ist, dass dies auch in .NET Standard 2.0 funktioniert.
Datum
60

Im Allgemeinen können Sie die System.Diagnostics.StackTraceKlasse System.Diagnostics.StackFrameverwenden GetMethod(), um ein System.Reflection.MethodBaseObjekt abzurufen , und dann die Methode verwenden, um ein Objekt abzurufen. Dieser Ansatz weist jedoch einige Einschränkungen auf:

  1. Es stellt den Laufzeitstapel - Optimierungen einen Inline könnten, und Sie werden nicht diese Methode in der Stack - Trace sehen.
  2. Es wird nicht alle nativen Frames anzeigen, so dass , wenn es auch eine Chance , Ihre Methode durch eine native Methode aufgerufen wird, wird dies nicht Arbeit, und es gibt in der Tat keine-Zeit verfügbar Art und Weise , es zu tun.

( HINWEIS: Ich werde nur auf die Antwort von Firas Assad eingehen .)

Alex Lyman
quelle
2
Können Sie im Debug-Modus mit deaktivierten Optimierungen sehen, welche Methode sich im Stack-Trace befindet?
AttackingHobo
1
@AttackingHobo: Ja - es sei denn, die Methode ist inline (Optimierungen aktiviert) oder ein nativer Frame, Sie werden es sehen.
Alex Lyman
38

Ab .NET 4.5 können Sie Anruferinformationsattribute verwenden :

  • CallerFilePath - Die Quelldatei, die die Funktion aufgerufen hat;
  • CallerLineNumber - Codezeile, die die Funktion aufgerufen hat;
  • CallerMemberName - Mitglied, das die Funktion aufgerufen hat.

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

 

Diese Funktion ist auch in ".NET Core" und ".NET Standard" vorhanden.

Verweise

  1. Microsoft - Anruferinformationen (C #)
  2. Microsoft - CallerFilePathAttributeKlasse
  3. Microsoft - CallerLineNumberAttributeKlasse
  4. Microsoft - CallerMemberNameAttributeKlasse
Ivan Pinto
quelle
15

Beachten Sie, dass dies im Release-Code aufgrund der Optimierung unzuverlässig ist. Wenn Sie die Anwendung im Sandbox-Modus (Netzwerkfreigabe) ausführen, können Sie den Stack-Frame überhaupt nicht abrufen.

Betrachten Sie aspektorientierte Programmierung (AOP) wie PostSharp , die Ihren Code nicht aufruft , sondern Ihren Code ändert und somit jederzeit weiß, wo er sich befindet.

Lasse V. Karlsen
quelle
Sie haben absolut Recht, dass dies in der Version nicht funktioniert. Ich bin mir nicht sicher, ob mir die Idee der Code-Injektion gefällt, aber ich denke, in gewissem Sinne erfordert eine Debug-Anweisung eine Code-Änderung, aber immer noch. Warum nicht einfach zu C-Makros zurückkehren? Es ist zumindest etwas, was Sie sehen können.
Ebyrob
9

Natürlich ist dies eine späte Antwort, aber ich habe eine bessere Option, wenn Sie .NET 4.5 oder mehr verwenden können:

internal static void WriteInformation<T>(string text, [CallerMemberName]string method = "")
{
    Console.WriteLine(DateTime.Now.ToString() + " => " + typeof(T).FullName + "." + method + ": " + text);
}

Dadurch werden das aktuelle Datum und die aktuelle Uhrzeit gedruckt, gefolgt von "Namespace.ClassName.MethodName" und endend mit ": text".
Beispielausgabe:

6/17/2016 12:41:49 PM => WpfApplication.MainWindow..ctor: MainWindow initialized

Beispielverwendung:

Logger.WriteInformation<MainWindow>("MainWindow initialized");
Camilo Terevinto
quelle
8
/// <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;
}
Flandern
quelle
Hoppla, ich hätte den Parameter "MethodAfter" etwas besser erklären sollen. Wenn Sie diese Methode in einer Funktion vom Typ "Protokoll" aufrufen, möchten Sie die Methode direkt nach der Funktion "Protokoll" abrufen. Sie würden also GetCallingMethod ("log") aufrufen. -Cheers
Flandern
6

Vielleicht suchen Sie so etwas:

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
4
private static MethodBase GetCallingMethod()
{
  return new StackFrame(2, false).GetMethod();
}

private static Type GetCallingType()
{
  return new StackFrame(2, false).GetMethod().DeclaringType;
}

Eine fantastische Klasse ist hier: http://www.csharp411.com/c-get-calling-method/

Tebo
quelle
StackFrame ist nicht zuverlässig. Wenn Sie "2 Frames" nach oben gehen, werden möglicherweise auch Methodenaufrufe zurückgesetzt.
user2864740
2

Ein anderer Ansatz, den ich verwendet habe, besteht darin, der fraglichen Methode einen Parameter hinzuzufügen. void Foo()Verwenden Sie beispielsweise anstelle von void Foo(string context). Übergeben Sie dann eine eindeutige Zeichenfolge, die den aufrufenden Kontext angibt.

Wenn Sie nur den Aufrufer / Kontext für die Entwicklung benötigen, können Sie den paramvor dem Versand entfernen .

GregUzelac
quelle
2

Versuchen Sie Folgendes, um den Methodennamen und den Klassennamen zu erhalten:

    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 );
    }
Arian
quelle
1
StackFrame caller = (new System.Diagnostics.StackTrace()).GetFrame(1);
string methodName = caller.GetMethod().Name;

wird genug sein, denke ich.

caner
quelle
1

Sehen Sie sich den Namen der Protokollierungsmethode in .NET an . Achten Sie darauf, es nicht im Produktionscode zu verwenden. StackFrame ist möglicherweise nicht zuverlässig ...

Yuval Peled
quelle
5
Eine Zusammenfassung des Inhalts wäre schön.
Peter Mortensen
1

Wir können auch Lambdas verwenden, um den Anrufer zu finden.

Angenommen, Sie haben eine von Ihnen definierte Methode:

public void MethodA()
    {
        /*
         * Method code here
         */
    }

und Sie möchten den Anrufer finden.

1 . Ändern Sie die Methodensignatur so, dass wir einen Parameter vom Typ Aktion haben (Func funktioniert auch):

public void MethodA(Action helperAction)
        {
            /*
             * Method code here
             */
        }

2 . Lambda-Namen werden nicht zufällig generiert. Die Regel scheint zu sein:> <CallerMethodName> __X wobei CallerMethodName durch die vorherige Funktion ersetzt wird und X ein Index ist.

private MethodInfo GetCallingMethodInfo(string funcName)
    {
        return GetType().GetMethod(
              funcName.Substring(1,
                                funcName.IndexOf("&gt;", 1, StringComparison.Ordinal) - 1)
              );
    }

3 . Wenn wir MethodA aufrufen, muss der Action / Func-Parameter von der Aufrufermethode generiert werden. Beispiel:

MethodA(() => {});

4 . In MethodA können wir jetzt die oben definierte Hilfsfunktion aufrufen und die MethodInfo der Aufrufermethode finden.

Beispiel:

MethodInfo callingMethodInfo = GetCallingMethodInfo(serverCall.Method.Name);
Smiron
quelle
0

Zusätzliche Informationen zur Antwort von Firas Assaad.

Ich habe new StackFrame(1).GetMethod().Name;in .net Core 2.1 mit Abhängigkeitsinjektion verwendet und erhalte die aufrufende Methode als 'Start'.

Ich habe es mit versucht [System.Runtime.CompilerServices.CallerMemberName] string callerName = "" und es gibt mir die richtige Aufrufmethode

cdev
quelle
-1
var callingMethod = new StackFrame(1, true).GetMethod();
string source = callingMethod.ReflectedType.FullName + ": " + callingMethod.Name;
Mauro Sala
quelle
1
Ich habe nicht abgelehnt, wollte aber beachten, dass das Hinzufügen von Text, um zu erklären, warum Sie sehr ähnliche Informationen veröffentlicht haben (Jahre später), den Wert der Frage erhöhen und weitere Abstimmungen vermeiden kann.
Shaun Wilson