Come posso trovare il metodo che ha chiamato il metodo corrente?


503

Quando accedo a C #, come posso imparare il nome del metodo che ha chiamato il metodo corrente? So tutto System.Reflection.MethodBase.GetCurrentMethod(), ma voglio fare un passo al di sotto di questo nella traccia dello stack. Ho considerato di analizzare la traccia dello stack, ma spero di trovare un modo più esplicito più pulito, qualcosa di simile Assembly.GetCallingAssembly()ai metodi.


22
Se si utilizza .net 4.5 beta +, è possibile utilizzare l'API CallerInformation .
Rohit Sharma,

5
Le informazioni sul chiamante sono anche molto più veloci
dove

4
Ho creato un rapido BenchmarkDotNet punto di riferimento dei tre metodi principali ( StackTrace, StackFramee CallerMemberName) e pubblicato i risultati come una sostanza per gli altri a vedere qui: gist.github.com/wilson0x4d/7b30c3913e74adf4ad99b09163a57a1f
Shaun Wilson

Risposte:


513

Prova questo:

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

one-liner:

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

Viene da Ottieni metodo di chiamata usando Reflection [C #] .


12
Puoi anche creare solo la cornice di cui hai bisogno, anziché l'intero stack:
Joel Coehoorn,

188
nuovo StackFrame (1) .GetMethod (). Name;
Joel Coehoorn,

12
Questo non è del tutto affidabile però. Vediamo se funziona in un commento! Prova quanto segue in un'applicazione console e vedi che le ottimizzazioni del compilatore lo infrangono. static void Main (string [] args) {CallIt (); } vuoto statico privato 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
Questo non funziona quando il compilatore è in linea o tail-call ottimizza il metodo, nel qual caso lo stack viene compresso e troverai altri valori del previsto. Quando lo usi solo nelle build di debug, funzionerà bene.
Abel

46
Quello che ho fatto in passato è aggiungere l'attributo del compilatore [MethodImplAttribute (MethodImplOptions.NoInlining)] prima del metodo che cercherà la traccia dello stack. Ciò garantisce che il compilatore non inserirà il metodo in linea e che la traccia dello stack conterrà il vero metodo di chiamata (nella maggior parte dei casi non sono preoccupato per la ricorsione della coda.)
Jordan Rieger

363

In C # 5 è possibile ottenere tali informazioni utilizzando le informazioni sul chiamante :

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

Puoi anche ottenere il [CallerFilePath]e [CallerLineNumber].


13
Ciao, non è C # 5, è disponibile in 4.5.
Dopo il

35
Le versioni di @AFract Language (C #) non sono le stesse della versione .NET.
kwesolowski,

6
@stuartd Sembra che sia [CallerTypeName]stato eliminato dall'attuale framework .Net (4.6.2) e Core CLR
Ph0en1x

4
@ Ph0en1x non è mai stato nel framework, il mio punto era che sarebbe stato utile se fosse, ad esempio come ottenere il nome del tipo di un CallerMember
stuartd

3
@DiegoDeberdt - Ho letto che l'uso di questo non ha alcun aspetto negativo poiché fa tutto il lavoro in fase di compilazione. Credo che sia preciso su ciò che ha chiamato il metodo.
cchamberlain,

109

È possibile utilizzare le informazioni sul chiamante e i parametri opzionali:

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

Questo test illustra questo:

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

Mentre StackTrace funziona abbastanza velocemente sopra e non sarebbe un problema di prestazioni nella maggior parte dei casi, le informazioni sul chiamante sono ancora molto più veloci. In un campione di 1000 iterazioni, l'ho registrato 40 volte più velocemente.


Disponibile solo da .Net 4.5
DerApe

1
Si noti che questo non funziona se il chiamante passa un agrument: CachingHelpers.WhoseThere("wrong name!");==> "wrong name!"perché CallerMemberNamesostituisce solo il valore predefinito.
Olivier Jacot-Descombes il

@ OlivierJacot-Descombes non funziona in questo modo allo stesso modo in cui un metodo di estensione non funzionerebbe se gli si passasse un parametro. potresti però usare un altro parametro stringa che potrebbe essere usato. Nota anche che il resharper ti darebbe un avvertimento se provassi a passare un argomento come hai fatto tu.
Colomba,

1
@dove è possibile passare qualsiasi thisparametro esplicito in un metodo di estensione. Inoltre, Olivier ha ragione, puoi passare un valore e [CallerMemberName]non viene applicato; invece funziona come una sostituzione in cui normalmente verrebbe utilizzato il valore predefinito. È un dato di fatto, se osserviamo l'IL possiamo vedere che il metodo risultante non è diverso da quello che sarebbe stato normalmente emesso per un [opt]arg, l'iniezione di CallerMemberNameè quindi un comportamento CLR. Infine, la documentazione: "Gli attributi di Info chiamante [...] influenzano il valore predefinito che viene passato quando l'argomento viene omesso "
Shaun Wilson

2
Questo è perfetto ed è asyncamichevole che StackFramenon ti aiuterà. Inoltre non influisce sulla chiamata da un lambda.
Aaron

65

Un rapido riepilogo dei 2 approcci con confronto della velocità come parte importante.

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

Determinare il chiamante in fase di compilazione

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

Determinare il chiamante usando lo stack

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

Confronto dei 2 approcci

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

Come vedi, usare gli attributi è molto, molto più veloce! Quasi 25 volte più veloce.


Questo metodo sembra essere un approccio superiore. Funziona anche in Xamarin senza problemi di spazi dei nomi non disponibili.
Lyndon Hughey,

63

Possiamo migliorare il codice di Mr Assad (l'attuale risposta accettata) solo un po 'istanziando solo il frame di cui abbiamo effettivamente bisogno piuttosto che l'intero stack:

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

Ciò potrebbe comportare un po 'meglio, anche se con ogni probabilità deve comunque utilizzare l'intero stack per creare quel singolo frame. Inoltre, ha ancora gli stessi avvertimenti che Alex Lyman ha sottolineato (l'ottimizzatore / codice nativo potrebbe corrompere i risultati). Infine, potresti voler controllare per essere sicuro new StackFrame(1)o .GetFrame(1)non tornare null, per quanto improbabile possa sembrare quella possibilità.

Vedi questa domanda correlata: puoi usare la riflessione per trovare il nome del metodo attualmente in esecuzione?


1
è anche possibile che sia new ClassName(…)uguale a null?
Visualizza nome

1
La cosa bella è che funziona anche in .NET Standard 2.0.
srsedate

60

In generale, è possibile utilizzare la System.Diagnostics.StackTraceclasse per ottenere un System.Diagnostics.StackFramee quindi utilizzare il GetMethod()metodo per ottenere un System.Reflection.MethodBaseoggetto. Tuttavia, ci sono alcuni avvertimenti su questo approccio:

  1. Rappresenta lo stack di runtime : le ottimizzazioni potrebbero incorporare un metodo e non vedrai quel metodo nella traccia dello stack.
  2. Esso non mostra alcuna frame nativi, quindi se c'è anche una possibilità il metodo viene chiamato da un metodo nativo, questo non è il lavoro, e non v'è in realtà non attualmente disponibile modo per farlo.

( NOTA: sto solo espandendo la risposta fornita da Firas Assad .)


2
In modalità debug con ottimizzazioni disattivate, saresti in grado di vedere qual è il metodo nella traccia dello stack?
Attaccando

1
@AttackingHobo: Sì - a meno che il metodo non sia inline (ottimizzazioni attivate) o in un frame nativo, lo vedrai.
Alex Lyman,

38

A partire da .NET 4.5 è possibile utilizzare gli attributi delle informazioni sul chiamante :

  • CallerFilePath - Il file sorgente che ha chiamato la funzione;
  • CallerLineNumber - Riga di codice che ha chiamato la funzione;
  • CallerMemberName - Membro che ha chiamato la funzione.

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

 

Questa funzione è presente anche in ".NET Core" e ".NET Standard".

Riferimenti

  1. Microsoft - Informazioni sul chiamante (C #)
  2. Microsoft - CallerFilePathAttributeClasse
  3. Microsoft - CallerLineNumberAttributeClasse
  4. Microsoft - CallerMemberNameAttributeClasse

15

Si noti che ciò non sarà affidabile nel codice di rilascio, a causa dell'ottimizzazione. Inoltre, l'esecuzione dell'applicazione in modalità sandbox (condivisione di rete) non ti permetterà di afferrare il frame dello stack.

Considera la programmazione orientata all'aspetto (AOP), come PostSharp , che invece di essere chiamato dal tuo codice, modifica il tuo codice e quindi sa dove si trova in ogni momento.


Hai assolutamente ragione che questo non funzionerà in versione. Non sono sicuro che mi piaccia l'idea dell'iniezione di codice, ma credo che in un certo senso un'istruzione di debug richieda una modifica del codice, ma comunque. Perché non tornare alle macro C? È almeno qualcosa che puoi vedere.
ebyrob,

9

Ovviamente questa è una risposta tardiva, ma ho un'opzione migliore se puoi usare .NET 4.5 o più:

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

Ciò stamperà la data e l'ora correnti, seguite da "Namespace.ClassName.MethodName" e terminando con ": text".
Uscita campione:

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

Esempio di utilizzo:

Logger.WriteInformation<MainWindow>("MainWindow initialized");

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

oops, avrei dovuto spiegare un po 'meglio il parametro "MethodAfter". Quindi, se stai chiamando questo metodo in una funzione di tipo "log", vorrai ottenere il metodo subito dopo la funzione "log". quindi chiameresti GetCallingMethod ("log"). -Cheers
Fiandre,

6

Forse stai cercando qualcosa del genere:

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

4
private static MethodBase GetCallingMethod()
{
  return new StackFrame(2, false).GetMethod();
}

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

Una fantastica classe è qui: http://www.csharp411.com/c-get-calling-method/


StackFrame non è affidabile. Salire "2 frame" potrebbe facilmente tornare indietro anche per le chiamate di metodo.
user2864740

2

Un altro approccio che ho usato è quello di aggiungere un parametro al metodo in questione. Ad esempio, anziché void Foo()utilizzare void Foo(string context). Quindi passa una stringa univoca che indica il contesto chiamante.

Se è necessario solo il chiamante / contesto per lo sviluppo, è possibile rimuovere paramprima della spedizione.


2

Per ottenere il nome del metodo e il nome della classe, provare questo:

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

1
StackFrame caller = (new System.Diagnostics.StackTrace()).GetFrame(1);
string methodName = caller.GetMethod().Name;

basterà, penso.



1

Possiamo anche usare lambda per trovare il chiamante.

Supponiamo di avere un metodo definito da te:

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

e vuoi trovare il chiamante.

1 . Cambia la firma del metodo in modo da avere un parametro di tipo Azione (funzionerà anche Func):

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

2 . I nomi lambda non vengono generati casualmente. La regola sembra essere:> <CallerMethodName> __X dove CallerMethodName è sostituito dalla funzione precedente e X è un indice.

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

3 . Quando chiamiamo MethodA il parametro Action / Func deve essere generato dal metodo caller. Esempio:

MethodA(() => {});

4 . All'interno di MethodA ora possiamo chiamare la funzione helper definita sopra e trovare il MethodInfo del metodo caller.

Esempio:

MethodInfo callingMethodInfo = GetCallingMethodInfo(serverCall.Method.Name);

0

Informazioni aggiuntive alla risposta Firas Assaad.

Ho usato new StackFrame(1).GetMethod().Name;in .net core 2.1 con iniezione di dipendenza e sto ricevendo il metodo di chiamata come 'Start'.

Ho provato con [System.Runtime.CompilerServices.CallerMemberName] string callerName = "" e mi dà il metodo di chiamata corretto


-1
var callingMethod = new StackFrame(1, true).GetMethod();
string source = callingMethod.ReflectedType.FullName + ": " + callingMethod.Name;

1
non ho effettuato il downgrade, ma ho notato che l'aggiunta di un testo per spiegare perché hai pubblicato informazioni molto simili (anni dopo) potrebbe aumentare il valore della domanda ed evitare ulteriori downgrade.
Shaun Wilson,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.