Puoi usare la riflessione per trovare il nome del metodo attualmente in esecuzione?


202

Come dice il titolo: Può la riflessione darti il ​​nome del metodo attualmente in esecuzione.

Sono propenso a non indovinare, a causa del problema di Heisenberg. Come si chiama un metodo che ti dirà il metodo corrente senza cambiare quello che è il metodo corrente? Ma spero che qualcuno possa provare che mi sbaglio.

Aggiornare:

  • Parte 2: Questo potrebbe essere usato anche per cercare nel codice una proprietà?
  • Parte 3: come sarebbe la performance?

Risultato finale
Ho imparato su MethodBase.GetCurrentMethod (). Ho anche imparato che non solo posso creare una traccia dello stack, ma posso creare solo il frame esatto di cui ho bisogno se lo desidero.

Per usarlo all'interno di una proprietà, basta prendere un .Substring (4) per rimuovere 'set_' o 'get_'.


Joel, so che è una vecchia domanda, ma cosa intendi per creare un frame esatto di un metodo?
Abhijeet,

Si riferisce a un elemento specifico nello stack di chiamate: la parte della traccia dello stack che conta.
Joel Coehoorn,

Risposte:


119

A partire da .NET 4.5 puoi anche usare [CallerMemberName]

Esempio: un setter proprietà (per rispondere alla parte 2):

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

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

Il compilatore fornirà letterali di stringa corrispondenti nei siti di chiamata, quindi non vi è praticamente alcun sovraccarico di prestazioni.


3
Questo è fantastico! Stavo usando il StackFrame(1)metodo descritto in altre risposte per il logging, che sembrava funzionare fino a quando il Jitter non decise di iniziare ad allineare le cose. Non volevo aggiungere l'attributo per impedire l'allineamento per motivi di prestazioni. L'uso [CallerMemberName]dell'approccio ha risolto il problema. Grazie!
Brian Rogers,

5
[CallerMemberName] è disponibile alla rete 4 con la build BCL piena
Venson

2
Tieni presente che l'utilizzo di StackFrame (1) in modalità debug dovrebbe funzionare. Ma quando si utilizza la modalità di rilascio durante la compilazione, potrebbero esserci alcune ottimizzazioni e lo stack potrebbe non essere quello che ti aspetti.
Axel O'Connell

Questo non restituirà il membro chiamante (ovvero SomeProperty), invece del metodo attualmente in esecuzione?
Lennart,

1
Sì, invocare il setter si tradurrà in una chiamata a OnPropertyChanged("SomeProperty")e nonOnPropertyChanged("SetProperty")
John Nilsson

189

Per i non asyncmetodi si può usare

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

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

Ricorda che per i asyncmetodi restituirà "MoveNext".


8
Essere consapevoli del fatto che ciò non sempre produce i risultati previsti. Vale a dire, piccoli metodi o proprietà sono spesso incorporati nelle build di rilascio, nel qual caso il risultato sarà invece il nome del metodo del chiamante.
Abel

5
Per quanto ne so, no. Perché in runtime, MSIL non è più disponibile dal puntatore di esecuzione (è JITted). Puoi ancora usare la riflessione se conosci il nome del metodo. Il punto è, quando sottolineato, il metodo attualmente in esecuzione è ora un altro metodo (cioè uno o più in alto nello stack). In altre parole, il metodo è scomparso. Anche se contrassegni il tuo metodo con NoInlining, c'è ancora la possibilità che venga ottimizzato per le chiamate in coda, nel qual caso anche esso è andato. Funzionerà, tuttavia, durante la compilazione del debug.
Abele

1
Per evitare inline aggiungere l'attributo [MethodImpl (MethodImplOptions.NoInlining)] all'inizio del metodo.
alex.peter

All'interno del asyncmetodo molto probabilmente otterrai "MoveNext" come nome del metodo.
Victor Yarema,

46

Lo snippet fornito da Lex era un po 'lungo, quindi sto sottolineando la parte importante poiché nessun altro ha usato la stessa identica tecnica:

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

Ciò dovrebbe restituire risultati identici a MethodBase.GetCurrentMethod (). Tecnica del nome , ma vale comunque la pena sottolineare che potrei implementarlo una volta nel suo metodo usando l'indice 1 per il metodo precedente e chiamarlo da un numero di proprietà diverse. Inoltre, restituisce solo un fotogramma anziché l'intera traccia dello stack:

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

È anche un one-liner;)


può essere una stringa statica pubblica GetPropertyName () in una classe di supporto? metodo statico?
Kiquenet,

2
Come nella risposta di Ed Guiness: lo stack può essere diverso nelle build di rilascio e il primo metodo potrebbe non essere lo stesso del metodo corrente in caso di inline o ottimizzazione delle chiamate in coda.
Abel

Vedi la risposta di John Nilsson per un buon modo per aggirare il problema inline se stai usando .Net 4.5.
Brian Rogers,

questa potrebbe essere migliore della risposta accettata e anche della risposta sopra
T.Todua,

16

Prova questo all'interno del metodo Main in un programma console vuoto:

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

Uscita console:
Main


12

Sì, sicuramente.

Se vuoi manipolare un oggetto, in realtà utilizzo una funzione come questa:

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

Questa linea:

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

Cammina nel frame dello stack per trovare il metodo di chiamata, quindi utilizziamo la riflessione per ottenere i valori delle informazioni sui parametri passati ad esso per una funzione di segnalazione errori generica. Per ottenere il metodo corrente è sufficiente utilizzare l'attuale stack frame (1).

Come altri hanno già detto per l'attuale nome dei metodi, puoi anche usare:

MethodBase.GetCurrentMethod()

Preferisco camminare nello stack perché, se guardi internamente a quel metodo, crea semplicemente StackCrawlMark comunque. Rivolgermi direttamente allo Stack mi sembra più chiaro

Post 4.5 ora puoi usare [CallerMemberNameAttribute] come parte dei parametri del metodo per ottenere una stringa del nome del metodo - questo può aiutare in alcuni scenari (ma in realtà nell'esempio sopra)

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

Questa sembrava essere principalmente una soluzione per il supporto di INotifyPropertyChanged in cui in precedenza avevi le stringhe disseminate per tutto il codice dell'evento.


Non sciocco; Li ho semplicemente passati. Probabilmente potresti fare qualcosa per renderlo più semplice da guardare, ma lo sforzo per premiare il rapporto sembra favorire il mantenimento della semplicità. Fondamentalmente lo sviluppatore copia semplicemente nell'elenco dei parametri della firma del metodo (rimuovendo i tipi ovviamente).
Lex,

che cos'è: ExceptionFormat e GetParameterList?
Kiquenet,

Molto tardi nella risposta, ma: ExceptionFormat è un formato di stringa costante e GetParameterList è semplicemente una funzione che formatta i parametri con i valori (potresti farlo in linea)
Lex

11

Confronto tra i modi per ottenere il nome del metodo - usando un costrutto di temporizzazione arbitrario in LinqPad:

CODICE

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

RISULTATI

riflesso riflesso

stacktrace stacktrace

inlineconstant inlineconstant

costante costante

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

Si noti che i metodi expre callermembernon sono del tutto "giusti". E lì vedi una ripetizione di un commento correlato che la riflessione è ~ 15 volte più veloce di stacktrace.


9

EDIT: MethodBase è probabilmente un modo migliore per ottenere solo il metodo in cui ti trovi (al contrario dell'intero stack chiamante). Sarei comunque preoccupato per l'allineamento.

È possibile utilizzare StackTrace con il metodo:

StackTrace st = new StackTrace(true);

E lo sguardo alle cornici:

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

Tuttavia, tieni presente che se il metodo è inline, non sarai all'interno del metodo che ritieni di essere. È possibile utilizzare un attributo per impedire l'allineamento:

[MethodImpl(MethodImplOptions.NoInlining)]

Inline a causa dell'ottimizzazione del rilascio è particolarmente complicato poiché il codice si comporterà diversamente nelle configurazioni di debug e rilascio. Fai attenzione alle piccole proprietà, sono le vittime più probabili di questo.
DK.

Mi chiedo perché woud usi new StackTrace(true)invece di new StackTrace(false). L'impostazione di tale trueopzione provocherà il tentativo di traccia dello stack di acquisire il nome del file, il numero di riga e così via, il che potrebbe rallentare questa chiamata. Altrimenti, una bella risposta
Ivaylo Slavov,

6

Il modo semplice di trattare è:

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

Se System.Reflection è incluso nel blocco using:

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

4

Che ne dici di questo:

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

Prova questo...

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

L'ho appena fatto con una semplice classe statica:

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

quindi nel tuo codice:

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()
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.