Pouvez-vous utiliser la réflexion pour trouver le nom de la méthode en cours d'exécution?

202

Comme le dit le titre: La réflexion peut-elle vous donner le nom de la méthode en cours d'exécution.

J'ai tendance à ne pas deviner, à cause du problème de Heisenberg. Comment appelez-vous une méthode qui vous indiquera la méthode actuelle sans changer ce qu'est la méthode actuelle? Mais j'espère que quelqu'un pourra me prouver le contraire.

Mettre à jour:

  • Partie 2: Cela pourrait-il également être utilisé pour rechercher dans le code d'une propriété?
  • Partie 3: À quoi ressemblerait la performance?

Résultat final
J'ai appris sur MethodBase.GetCurrentMethod (). J'ai également appris que non seulement je peux créer une trace de pile, mais je ne peux créer que le cadre exact dont j'ai besoin si je le souhaite.

Pour l'utiliser dans une propriété, prenez simplement un .Substring (4) pour supprimer le 'set_' ou le 'get_'.

Joel Coehoorn
la source
Joel, je sais que c'est une vieille question, mais que voulez-vous dire par créer le cadre exact d'une méthode?
Abhijeet du
Il fait référence à un élément spécifique de la pile d'appels: la partie de la trace de pile qui compte.
Joel Coehoorn

Réponses:

119

Depuis .NET 4.5, vous pouvez également utiliser [CallerMemberName]

Exemple: un configurateur de propriétés (pour répondre à la partie 2):

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

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

Le compilateur fournira des littéraux de chaîne correspondants sur les sites d'appels, il n'y a donc pratiquement pas de surcharge de performances.

John Nilsson
la source
3
C'est bien! J'utilisais la StackFrame(1)méthode décrite dans d'autres réponses pour la journalisation, qui semblait fonctionner jusqu'à ce que le Jitter décide de commencer à aligner les choses. Je ne voulais pas ajouter l'attribut pour empêcher l'inline pour des raisons de performances. L'utilisation de l' [CallerMemberName]approche a résolu le problème. Merci!
Brian Rogers
5
[CallerMemberName] est disponible au net 4 avec la build BCL emballée
Venson
2
Prenez en compte que l'utilisation de StackFrame (1) en mode débogage devrait fonctionner. Mais lors de l'utilisation du mode de publication lors de la compilation, il peut y avoir des optimisations et la pile peut ne pas être celle que vous attendez.
Axel O'Connell
Cela ne retournera-t-il pas le membre appelant (ie SomeProperty), au lieu de la méthode en cours d'exécution?
Lennart
1
Oui, invoquer le passeur entraînera un appel à OnPropertyChanged("SomeProperty")et nonOnPropertyChanged("SetProperty")
John Nilsson
189

Pour les non- asyncméthodes on peut utiliser

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

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

N'oubliez pas que pour les asyncméthodes, il renverra "MoveNext".

Ed Guiness
la source
8
Sachez que cela ne donne pas toujours les résultats escomptés. C'est-à-dire que les petites méthodes ou propriétés sont souvent intégrées dans les versions de version, auquel cas le résultat sera le nom de la méthode de l'appelant à la place.
Abel
5
Pour autant que je sache, non. Parce qu'à l'exécution, le MSIL n'est plus disponible à partir du pointeur d'exécution (c'est JITted). Vous pouvez toujours utiliser la réflexion si vous connaissez le nom de la méthode. Le fait est que, une fois en ligne, la méthode en cours d'exécution est maintenant une autre méthode (c'est-à-dire une ou plusieurs plus haut dans la pile). En d'autres termes, la méthode a disparu. Même si vous marquez votre méthode avec NoInlining, il y a toujours une chance qu'elle soit optimisée pour les appels de queue, auquel cas elle a également disparu. Cela fonctionnera, cependant, pendant la version de débogage.
Abel
1
Afin d'éviter l'inline, ajoutez l'attribut [MethodImpl (MethodImplOptions.NoInlining)] au-dessus de la méthode.
alex.peter
Dans la asyncméthode, vous obtiendrez très probablement "MoveNext" comme nom de méthode.
Victor Yarema
46

L'extrait fourni par Lex était un peu long, donc je souligne la partie importante car personne d'autre n'a utilisé exactement la même technique:

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

Cela devrait renvoyer des résultats identiques à la méthode MethodBase.GetCurrentMethod (). Name , mais cela vaut quand même la peine d'être souligné car je pourrais l'implémenter une fois dans sa propre méthode en utilisant l'index 1 pour la méthode précédente et l'appeler à partir d'un certain nombre de propriétés différentes. En outre, il ne renvoie qu'une seule image plutôt que la trace de la pile entière:

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

C'est aussi une doublure;)

Joel Coehoorn
la source
peut être une chaîne statique publique GetPropertyName () dans une classe d'assistance? méthode statique?
Kiquenet
2
Identique à la réponse d'Ed Guiness: la pile peut être différente dans les builds des versions et la première méthode peut ne pas être la même que la méthode actuelle en cas d'optimisation en ligne ou en queue d'appel.
Abel
Voir la réponse de John Nilsson pour une bonne façon de contourner le problème de l'inline si vous utilisez .Net 4.5.
Brian Rogers
cela pourrait être mieux que la réponse acceptée et la réponse ci-dessus aussi
T.Todua
16

Essayez ceci dans la méthode Main dans un programme console vide:

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

Sortie console:
Main

Lars Mæhlum
la source
12

Oui définitivement.

Si vous voulez qu'un objet manipule, j'utilise en fait une fonction comme celle-ci:

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

Cette ligne:

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

Parcourt le cadre de pile pour trouver la méthode d'appel, puis nous utilisons la réflexion pour obtenir les valeurs des informations de paramètres qui lui sont transmises pour une fonction de rapport d'erreur générique. Pour obtenir la méthode actuelle, utilisez simplement le cadre de pile actuel (1) à la place.

Comme d'autres l'ont dit pour le nom actuel des méthodes, vous pouvez également utiliser:

MethodBase.GetCurrentMethod()

Je préfère parcourir la pile parce que si vous regardez en interne cette méthode, cela crée tout de même un StackCrawlMark. L'adressage direct de la pile me semble plus clair

Après la 4.5, vous pouvez maintenant utiliser le [CallerMemberNameAttribute] dans le cadre des paramètres de la méthode pour obtenir une chaîne du nom de la méthode - cela peut aider dans certains scénarios (mais vraiment dans l'exemple de l'exemple ci-dessus)

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

Cela semblait être principalement une solution pour le support INotifyPropertyChanged où auparavant vous aviez des chaînes jonchées tout au long de votre code d'événement.

Lex
la source
Pas idiot; Je les ai simplement transmis. Vous pourriez probablement faire quelque chose pour le rendre plus simple à regarder, mais le rapport effort / récompense semblait favoriser la simplicité. Essentiellement, le développeur copie simplement dans la liste des paramètres de la signature de la méthode (en supprimant bien sûr les types).
Lex
ce que c'est: ExceptionFormat et GetParameterList?
Kiquenet
Bien en retard dans la réponse mais: ExceptionFormat est un format de chaîne constant et GetParameterList est une fonction simple qui formate les paramètres avec les valeurs (vous pouvez le faire en ligne)
Lex
11

Comparaison des façons d'obtenir le nom de la méthode - en utilisant une construction de synchronisation arbitraire dans 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;
    }
}

RÉSULTATS

réflexion réflexion

stacktrace stacktrace

inlineconstant inlineconstant

constante constante

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

Notez que les méthodes expret callermemberne sont pas tout à fait "correctes". Et là, vous voyez une répétition d' un commentaire connexe que la réflexion est ~ 15x plus rapide que stacktrace.

drzaus
la source
9

EDIT: MethodBase est probablement un meilleur moyen d'obtenir simplement la méthode dans laquelle vous vous trouvez (par opposition à la pile d'appel entière). Je serais toujours inquiet de l'inliner cependant.

Vous pouvez utiliser un StackTrace dans la méthode:

StackTrace st = new StackTrace(true);

Et le regard sur les cadres:

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

Cependant, sachez que si la méthode est intégrée, vous ne serez pas à l'intérieur de la méthode que vous pensez être. Vous pouvez utiliser un attribut pour empêcher l'incrustation:

[MethodImpl(MethodImplOptions.NoInlining)]
denis phillips
la source
L'inline en raison de l'optimisation des versions est particulièrement délicat car le code se comportera différemment dans les configurations de débogage et de version. Attention aux petites propriétés, elles en sont les victimes les plus probables.
DK.
Je me demande pourquoi utiliser new StackTrace(true)plutôt new StackTrace(false). Réglage que pour trueprovoquera la trace de la pile à atttempt capturer le nom du fichier, le numéro de ligne et etc, ce qui pourrait faire cet appel plus lent. Sinon, une belle réponse
Ivaylo Slavov
6

La façon la plus simple de traiter est la suivante:

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

Si System.Reflection est inclus dans le bloc using:

MethodBase.GetCurrentMethod().DeclaringType.FullName + "." + MethodBase.GetCurrentMethod().Name;
Requin
la source
4

Que diriez-vous ceci:

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
jesal
la source
0

Essaye ça...

    /// <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
la source
0

Je viens de le faire avec une classe statique simple:

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

puis dans votre 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
la source
-1
new StackTrace().ToString().Split("\r\n",StringSplitOptions.RemoveEmptyEntries)[0].Replace("at ","").Trim()
Baglay Vyacheslav
la source