Comment puis-je trouver la méthode qui a appelé la méthode actuelle?

503

Lors de la connexion en C #, comment puis-je connaître le nom de la méthode qui a appelé la méthode actuelle? Je sais tout System.Reflection.MethodBase.GetCurrentMethod(), mais je veux aller plus loin dans la trace de la pile. J'ai envisagé d'analyser la trace de la pile, mais j'espère trouver un moyen plus clair et plus explicite, quelque chose comme Assembly.GetCallingAssembly()mais pour les méthodes.

flipdoubt
la source
22
Si vous utilisez .net 4.5 beta +, vous pouvez utiliser l' API CallerInformation .
Rohit Sharma
5
L'information sur les appelants est également beaucoup plus rapide
plongé
4
J'ai créé un benchmark BenchmarkDotNet rapide des trois méthodes principales ( StackTrace, StackFrameet CallerMemberName) et publié les résultats sous forme de résumé pour que d'autres puissent les voir ici: gist.github.com/wilson0x4d/7b30c3913e74adf4ad99b09163a57a1f
Shaun Wilson

Réponses:

513

Essaye ça:

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

bon mot:

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

Il s'agit de la méthode Get Calling utilisant la réflexion [C #] .

Firas Assaad
la source
12
Vous pouvez également créer uniquement le cadre dont vous avez besoin, plutôt que la pile entière:
Joel Coehoorn
188
new StackFrame (1) .GetMethod (). Name;
Joel Coehoorn
12
Ce n'est cependant pas entièrement fiable. Voyons voir si cela fonctionne dans un commentaire! Essayez ce qui suit dans une application console et vous voyez que les optimisations du compilateur la cassent. statique void Main (chaîne [] args) {CallIt (); } privé statique vide CallIt () {Final (); } static void Final () {StackTrace trace = new StackTrace (); Trame StackFrame = trace.GetFrame (1); Console.WriteLine ("{0}. {1} ()", frame.GetMethod (). DeclaringType.FullName, frame.GetMethod (). Name); }
BlackWasp
10
Cela ne fonctionne pas lorsque le compilateur en ligne ou l'appel de queue optimise la méthode, auquel cas la pile est réduite et vous trouverez d'autres valeurs que prévu. Lorsque vous n'utilisez cela que dans les versions de débogage, cela fonctionnera bien.
Abel
46
Ce que j'ai fait dans le passé, c'est ajouter l'attribut du compilateur [MethodImplAttribute (MethodImplOptions.NoInlining)] avant la méthode qui recherchera la trace de la pile. Cela garantit que le compilateur n'alignera pas la méthode et que la trace de pile contiendra la véritable méthode d'appel (je ne suis pas inquiet de la récursivité de la queue dans la plupart des cas.)
Jordan Rieger
363

En C # 5, vous pouvez obtenir ces informations en utilisant les informations de l' appelant :

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

Vous pouvez également obtenir le [CallerFilePath]et [CallerLineNumber].

Coincoin
la source
13
Bonjour, ce n'est pas du C # 5, il est disponible en 4.5.
AFract
35
Les versions @AFract Language (C #) ne sont pas identiques à la version .NET.
kwesolowski
6
@stuartd Looks like a [CallerTypeName]été supprimé du framework .Net actuel (4.6.2) et du Core CLR
Ph0en1x
4
@ Ph0en1x ce n'était jamais dans le cadre, mon point de vue était que ce serait pratique si c'était le cas, par exemple comment obtenir le nom de type d'un CallerMember
stuartd
3
@DiegoDeberdt - J'ai lu que son utilisation n'a pas d'inconvénients de réflexion car elle fait tout le travail au moment de la compilation. Je pense qu'il est exact de ce que l'on appelle la méthode.
cchamberlain
109

Vous pouvez utiliser les informations sur l'appelant et les paramètres facultatifs:

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

Ce test illustre cela:

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

Bien que StackTrace fonctionne assez rapidement ci-dessus et ne soit pas un problème de performances dans la plupart des cas, les informations sur l'appelant sont encore beaucoup plus rapides. Dans un échantillon de 1000 itérations, je l'ai cadencé 40 fois plus vite.

Colombe
la source
Uniquement disponible à partir de .Net 4.5
DerApe
1
Notez que cela ne fonctionne pas si l'appelant passe un agrément: CachingHelpers.WhoseThere("wrong name!");==> "wrong name!"car le CallerMemberNameis ne remplace que la valeur par défaut.
Olivier Jacot-Descombes
@ OlivierJacot-Descombes ne fonctionne pas de cette manière de la même manière qu'une méthode d'extension ne fonctionnerait pas si vous lui passiez un paramètre. vous pourriez bien un autre paramètre de chaîne qui pourrait être utilisé. Notez également que resharper vous avertirait si vous tentiez de passer un argument comme vous l'avez fait.
colombe du
1
@dove vous pouvez passer n'importe quel thisparamètre explicite dans une méthode d'extension. De plus, Olivier a raison, vous pouvez passer une valeur et [CallerMemberName]n'est pas appliqué; au lieu de cela, il fonctionne comme un remplacement où la valeur par défaut serait normalement utilisée. En fait, si nous regardons l'IL, nous pouvons voir que la méthode résultante n'est pas différente de ce qui aurait normalement été émis pour un [opt]arg, l'injection de CallerMemberNameest donc un comportement CLR. Enfin, la documentation: "Les attributs des informations sur l'appelant [...] affectent la valeur par défaut transmise lorsque l'argument est omis "
Shaun Wilson
2
C'est parfait et asyncconvivial qui StackFramene vous aidera pas. N'affecte pas non plus l'appel d'une lambda.
Aaron
65

Un récapitulatif rapide des 2 approches avec comparaison de vitesse étant la partie importante.

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

Déterminer l'appelant au moment de la compilation

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

Déterminer l'appelant à l'aide de la pile

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

Comparaison des 2 approches

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

Donc, vous voyez, l'utilisation des attributs est beaucoup, beaucoup plus rapide! En fait, près de 25 fois plus rapide.

Tikall
la source
Cette méthode semble être une approche supérieure. Il fonctionne également dans Xamarin sans problème d'espace de noms indisponible.
lyndon hughey
63

Nous pouvons améliorer un peu le code de M. Assad (la réponse actuellement acceptée) en instanciant uniquement le cadre dont nous avons réellement besoin plutôt que la pile entière:

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

Cela pourrait fonctionner un peu mieux, mais selon toute vraisemblance, il doit encore utiliser la pile complète pour créer cette seule image. En outre, il a toujours les mêmes mises en garde qu'Alex Lyman a souligné (l'optimiseur / code natif pourrait corrompre les résultats). Enfin, vous voudrez peut-être vérifier pour être sûr que new StackFrame(1)ou .GetFrame(1)ne revenez pas null, aussi improbable que cela puisse paraître.

Voir cette question connexe: pouvez-vous utiliser la réflexion pour trouver le nom de la méthode en cours d'exécution?

Joel Coehoorn
la source
1
est-il même possible que new ClassName(…)égal à null?
Nom d'affichage
1
Ce qui est bien, c'est que cela fonctionne également dans .NET Standard 2.0.
srsedate
60

En général, vous pouvez utiliser la System.Diagnostics.StackTraceclasse pour obtenir un System.Diagnostics.StackFrame, puis utiliser la GetMethod()méthode pour obtenir un System.Reflection.MethodBaseobjet. Cependant, il y a quelques mises en garde à cette approche:

  1. Il représente la pile d' exécution - les optimisations pourraient aligner une méthode, et vous ne verrez pas cette méthode dans la trace de la pile.
  2. Il ne montrera aucun cadre natif, donc s'il y a même une chance que votre méthode soit appelée par une méthode native, cela ne fonctionnera pas , et il n'y a en fait aucun moyen actuellement disponible pour le faire.

( REMARQUE: je développe simplement la réponse fournie par Firas Assad .)

Alex Lyman
la source
2
En mode débogage avec les optimisations désactivées, pourriez-vous voir quelle est la méthode dans la trace de la pile?
AttackingHobo
1
@AttackingHobo: Oui - sauf si la méthode est intégrée (optimisations activées) ou un cadre natif, vous le verrez.
Alex Lyman,
38

Depuis .NET 4.5, vous pouvez utiliser les attributs d' informations sur l'appelant :

  • CallerFilePath - Le fichier source qui a appelé la fonction;
  • CallerLineNumber - Ligne de code qui a appelé la fonction;
  • CallerMemberName - Membre qui a appelé la fonction.

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

 

Cette fonctionnalité est également présente dans ".NET Core" et ".NET Standard".

Références

  1. Microsoft - Informations sur l'appelant (C #)
  2. Microsoft - CallerFilePathAttributeClasse
  3. Microsoft - CallerLineNumberAttributeClasse
  4. Microsoft - CallerMemberNameAttributeClasse
Ivan Pinto
la source
15

Notez que cela ne sera pas fiable dans le code de version, en raison de l'optimisation. De plus, l'exécution de l'application en mode sandbox (partage réseau) ne vous permettra pas du tout de saisir le cadre de la pile.

Considérez la programmation orientée aspect (AOP), comme PostSharp , qui au lieu d'être appelée à partir de votre code, modifie votre code et sait ainsi où il se trouve à tout moment.

Lasse V. Karlsen
la source
Vous avez absolument raison que cela ne fonctionnera pas dans la version. Je ne suis pas sûr que j'aime l'idée d'injection de code, mais je suppose que dans un sens, une instruction de débogage nécessite une modification de code, mais quand même. Pourquoi ne pas simplement revenir aux macros C? C'est au moins quelque chose que vous pouvez voir.
ebyrob
9

Évidemment, c'est une réponse tardive, mais j'ai une meilleure option si vous pouvez utiliser .NET 4.5 ou plus:

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

Cela affichera la date et l'heure actuelles, suivies de "Namespace.ClassName.MethodName" et se terminant par ": text".
Exemple de sortie:

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

Exemple d'utilisation:

Logger.WriteInformation<MainWindow>("MainWindow initialized");
Camilo Terevinto
la source
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;
}
Flandre
la source
oups, j'aurais dû expliquer un peu mieux le paramètre "MethodAfter". Donc, si vous appelez cette méthode dans une fonction de type "log", vous voudrez obtenir la méthode juste après la fonction "log". vous appelez donc GetCallingMethod ("log"). -Cheers
Flandre
6

Peut-être que vous cherchez quelque chose comme ça:

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

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

Une classe fantastique est ici: http://www.csharp411.com/c-get-calling-method/

Tebo
la source
StackFrame n'est pas fiable. Remonter "2 images" pourrait facilement revenir en arrière aussi pour les appels de méthode.
user2864740
2

Une autre approche que j'ai utilisée consiste à ajouter un paramètre à la méthode en question. Par exemple, au lieu de void Foo(), utilisez void Foo(string context). Passez ensuite une chaîne unique qui indique le contexte d'appel.

Si vous n'avez besoin que de l'appelant / du contexte pour le développement, vous pouvez supprimer le paramavant l'expédition.

GregUzelac
la source
2

Pour obtenir le nom de la méthode et le nom de la classe, essayez ceci:

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

sera suffisant, je pense.

chancre
la source
1

Jetez un œil au nom de la méthode de journalisation dans .NET . Méfiez-vous de l'utiliser dans le code de production. StackFrame n'est peut-être pas fiable ...

Yuval Peled
la source
5
Un résumé du contenu serait bien.
Peter Mortensen
1

Nous pouvons également utiliser des lambda pour trouver l'appelant.

Supposons que vous ayez défini une méthode:

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

et vous voulez trouver son appelant.

1 . Modifiez la signature de la méthode pour avoir un paramètre de type Action (Func fonctionnera également):

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

2 . Les noms lambda ne sont pas générés de manière aléatoire. La règle semble être:> <CallerMethodName> __X où CallerMethodName est remplacé par la fonction précédente et X est un index.

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

3 . Lorsque nous appelons MethodA, le paramètre Action / Func doit être généré par la méthode de l'appelant. Exemple:

MethodA(() => {});

4 . À l'intérieur de MethodA, nous pouvons maintenant appeler la fonction d'assistance définie ci-dessus et trouver le MethodInfo de la méthode de l'appelant.

Exemple:

MethodInfo callingMethodInfo = GetCallingMethodInfo(serverCall.Method.Name);
smiron
la source
0

Informations supplémentaires à la réponse de Firas Assaad.

J'ai utilisé new StackFrame(1).GetMethod().Name;dans .net core 2.1 avec injection de dépendances et je reçois la méthode d'appel en tant que 'Start'.

J'ai essayé avec [System.Runtime.CompilerServices.CallerMemberName] string callerName = "" et ça me donne la bonne méthode d'appel

cdev
la source
-1
var callingMethod = new StackFrame(1, true).GetMethod();
string source = callingMethod.ReflectedType.FullName + ": " + callingMethod.Name;
Mauro Sala
la source
1
Je n'ai pas fait de downvote, mais je voulais noter que l'ajout de texte pour expliquer pourquoi vous avez publié des informations très similaires (des années plus tard) peut augmenter la valeur de la question et éviter de continuer à downvoter.
Shaun Wilson