Como posso encontrar o método que chamou o método atual?

503

Ao efetuar logon em C #, como posso aprender o nome do método que chamou o método atual? Eu sei tudo sobre System.Reflection.MethodBase.GetCurrentMethod(), mas quero dar um passo abaixo disso no rastreamento de pilha. Eu considerei analisar o rastreamento da pilha, mas espero encontrar uma maneira mais explícita e limpa, algo como Assembly.GetCallingAssembly()mas para métodos.

duvidoso
fonte
22
Se você estiver usando o .net 4.5 beta +, poderá usar a API CallerInformation .
Rohit Sharma
5
Informações sobre chamadas também são muito mais rápidas
dove
4
Eu criei um rápido BenchmarkDotNet referência dos três métodos principais ( StackTrace, StackFramee CallerMemberName) e postou os resultados como uma essência para que outros vejam aqui: gist.github.com/wilson0x4d/7b30c3913e74adf4ad99b09163a57a1f
Shaun Wilson

Respostas:

513

Tente o seguinte:

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

uma linha:

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

É do método Get Calling usando Reflection [C #] .

Firas Assaad
fonte
12
Você também pode criar apenas o quadro que precisa, em vez de toda a pilha:
Joel Coehoorn 6/08
187
novo StackFrame (1) .GetMethod (). Name;
Joel Coehoorn
12
Isso não é totalmente confiável. Vamos ver se isso funciona em um comentário! Tente o seguinte em um aplicativo de console e você verá que as otimizações do compilador o quebram. static void Main (string [] args) {CallIt (); } private static void CallIt () {Final (); static void Final () {Rastreio de StackTrace = new StackTrace (); Frame StackFrame = trace.GetFrame (1); Console.WriteLine ("{0}. {1} ()", frame.GetMethod (). DeclaringType.FullName, frame.GetMethod (). Name); }
BlackWasp
10
Isso não funciona quando o compilador embutido ou a chamada final otimiza o método; nesse caso, a pilha é recolhida e você encontrará outros valores além do esperado. Quando você o usa apenas nas compilações de depuração, ele funcionará bem.
Abel
46
O que eu fiz no passado foi adicionar o atributo do compilador [MethodImplAttribute (MethodImplOptions.NoInlining)] antes do método que procurará o rastreamento da pilha. Isso garante que o compilador não in-line o método e o rastreamento de pilha irá conter o método de chamada verdadeira (Eu não estou preocupado com cauda-recursão na maioria dos casos.)
Jordan Rieger
363

No C # 5, você pode obter essas informações usando as informações do chamador :

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

Você também pode obter o [CallerFilePath]e [CallerLineNumber].

Coincoin
fonte
13
Olá, não é C # 5, está disponível no 4.5.
AFract
35
As versões do @AFract Language (C #) não são iguais às da versão .NET.
Kwesolowski
6
@stuartd Looks como [CallerTypeName]foi retirado do quadro actual .Net (4.6.2) e Core CLR
Ph0en1x
4
@ Ph0en1x nunca foi no quadro, o meu ponto era que seria útil se fosse, por exemplo, como obter o nome Tipo de um CallerMember
stuartd
3
@ DiegoDeberdt - Eu li que usar isso não tem desvantagens de reflexão, uma vez que faz todo o trabalho em tempo de compilação. Eu acredito que é preciso o que chamou o método.
Cchamberlain 28/05
109

Você pode usar as informações do chamador e os parâmetros opcionais:

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

Este teste ilustra isso:

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

Enquanto o StackTrace trabalha muito rápido acima e não seria um problema de desempenho na maioria dos casos, as informações do chamador ainda são muito mais rápidas. Em uma amostra de 1000 iterações, registrei o clock 40 vezes mais rápido.

pomba
fonte
Somente disponível no .Net 4.5
DerApe /
1
Observe que isso não funciona, se o chamador passar um agrument: CachingHelpers.WhoseThere("wrong name!");==> "wrong name!"porque o CallerMemberNameé apenas substitui o valor padrão.
Olivier Jacot-Descombes
@ OlivierJacot-Descombes is não funciona dessa maneira da mesma maneira que um método de extensão não funcionaria se você passasse um parâmetro para ele. você poderia encontrar outro parâmetro de string que pudesse ser usado. Observe também que o re-afiador enviaria um aviso se você tentasse passar um argumento como você.
pomba
1
@dove, você pode passar qualquer thisparâmetro explícito para um método de extensão. Além disso, Olivier está correto, você pode passar um valor e [CallerMemberName]não é aplicado; em vez disso, funciona como uma substituição onde o valor padrão normalmente seria usado. De fato, se olharmos para a IL, podemos ver que o método resultante não é diferente do que normalmente seria emitido para um [opt]arg, a injeção de CallerMemberNameé, portanto, um comportamento CLR. Por fim, os documentos: "Os atributos [...] de informações do chamador afetam o valor padrão que é passado quando o argumento é omitido "
Shaun Wilson
2
Isso é perfeito e é asyncamigável, o que StackFramenão o ajudará. Também não afeta ser chamado de um lambda.
Aaron
65

Uma rápida recapitulação das 2 abordagens, com a comparação de velocidade sendo a parte importante.

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

Determinando o chamador em tempo de compilação

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

Determinando o chamador usando a pilha

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

Comparação das 2 abordagens

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

Então você vê, usar os atributos é muito, muito mais rápido! Quase 25x mais rápido, de fato.

Tikall
fonte
Este método parece ser uma abordagem superior. Também funciona no Xamarin sem o problema de os espaços para nome não estarem disponíveis.
lyndon Hughey
63

Podemos melhorar um pouco o código do Sr. Assad (a resposta atual aceita) instanciando apenas o quadro que realmente precisamos em vez da pilha inteira:

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

Isso pode ter um desempenho um pouco melhor, embora, com toda a probabilidade, ele ainda precise usar a pilha completa para criar esse único quadro. Além disso, ele ainda possui as mesmas ressalvas que Alex Lyman apontou (o otimizador / código nativo pode corromper os resultados). Finalmente, você pode querer verificar para ter certeza de que new StackFrame(1)ou .GetFrame(1)não voltar null, por mais improvável que essa possibilidade possa parecer.

Veja esta pergunta relacionada: Você pode usar a reflexão para encontrar o nome do método atualmente em execução?

Joel Coehoorn
fonte
1
é possível que new ClassName(…)seja igual a nulo?
Exibir nome
1
O interessante é que isso também funciona no .NET Standard 2.0.
srsedate
60

Em geral, você pode usar a System.Diagnostics.StackTraceclasse para obter um System.Diagnostics.StackFramee, em seguida, usar o GetMethod()método para obter um System.Reflection.MethodBaseobjeto. No entanto, existem algumas ressalvas nessa abordagem:

  1. Ele representa a pilha de tempo de execução - as otimizações poderiam incorporar um método e você não o verá no rastreamento da pilha.
  2. Ele não mostrará nenhum quadro nativo; portanto, se houver uma chance de seu método ser chamado por um método nativo, isso não funcionará e, de fato, não há uma maneira atualmente disponível de fazê-lo.

( NOTA: Estou apenas expandindo a resposta fornecida por Firas Assad .)

Alex Lyman
fonte
2
No modo de depuração com otimizações desativadas, você conseguiria ver qual é o método no rastreamento de pilha?
AttackingHobo
1
@ AttackingHobo: Sim - a menos que o método seja incorporado (otimizações ativadas) ou um quadro nativo, você o verá.
Alex Lyman
38

A partir do .NET 4.5, você pode usar os Atributos de Informações do Chamador :

  • CallerFilePath - O arquivo de origem que chamou a função;
  • CallerLineNumber - Linha de código que chamou a função;
  • CallerMemberName - Membro que chamou a função.

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

 

Esse recurso também está presente no ".NET Core" e no ".NET Standard".

Referências

  1. Microsoft - Informações sobre chamadas (C #)
  2. Microsoft - CallerFilePathAttributeClasse
  3. Microsoft - CallerLineNumberAttributeClasse
  4. Microsoft - CallerMemberNameAttributeClasse
Ivan Pinto
fonte
15

Observe que isso não será confiável no código de liberação, devido à otimização. Além disso, a execução do aplicativo no modo sandbox (compartilhamento de rede) não permitirá que você pegue o quadro da pilha.

Considere a programação orientada a aspectos (AOP), como o PostSharp , que em vez de ser chamado pelo código, modifica o código e, portanto, sabe onde está o tempo todo.

Lasse V. Karlsen
fonte
Você está absolutamente correto de que isso não funcionará na versão. Não tenho certeza se gosto da idéia de injeção de código, mas acho que, em certo sentido, uma instrução de depuração requer modificação de código, mas ainda assim. Por que não voltar às macros C? É pelo menos algo que você pode ver.
ebyrob
9

Obviamente, esta é uma resposta tardia, mas eu tenho uma opção melhor se você pode usar o .NET 4.5 ou mais:

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

Isso imprimirá a data e hora atuais, seguidas por "Namespace.ClassName.MethodName" e terminando com ": text".
Saída de amostra:

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

Uso da amostra:

Logger.WriteInformation<MainWindow>("MainWindow initialized");
Camilo Terevinto
fonte
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;
}
Flandres
fonte
Opa, eu deveria ter explicado o parâmetro "MethodAfter" um pouco melhor. Portanto, se você estiver chamando esse método em uma função do tipo "log", desejará obter o método logo após a função "log". então você chamaria GetCallingMethod ("log"). -Cheers
Flandres,
6

Talvez você esteja procurando algo parecido com isto:

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

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

Uma aula fantástica está aqui: http://www.csharp411.com/c-get-calling-method/

Tebo
fonte
StackFrame não é confiável. Subir "2 quadros" pode facilmente voltar também às chamadas de método.
precisa saber é o seguinte
2

Outra abordagem que usei é adicionar um parâmetro ao método em questão. Por exemplo, em vez de void Foo(), use void Foo(string context). Em seguida, passe uma string exclusiva que indica o contexto de chamada.

Se você precisar apenas do chamador / contexto para desenvolvimento, poderá remover o paramantes do envio.

GregUzelac
fonte
2

Para obter o Nome do método e o Nome da classe, tente o seguinte:

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

será o suficiente, eu acho.

caner
fonte
1

Dê uma olhada no nome do método de log no .NET . Cuidado ao usá-lo no código de produção. O StackFrame pode não ser confiável ...

Yuval Peled
fonte
5
Um resumo do conteúdo seria bom.
Peter Mortensen
1

Também podemos usar lambda's para encontrar o chamador.

Suponha que você tenha um método definido por você:

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

e você quer encontrar o chamador.

1 . Altere a assinatura do método para que tenhamos um parâmetro do tipo Action (o Func também funcionará):

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

2 . Nomes Lambda não são gerados aleatoriamente. A regra parece ser:> <CallerMethodName> __X em que CallerMethodName é substituído pela função anterior e X é um índice.

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

3 . Quando chamamos MethodA, o parâmetro Action / Func deve ser gerado pelo método chamador. Exemplo:

MethodA(() => {});

4 . Dentro do MethodA agora podemos chamar a função helper definida acima e encontrar o MethodInfo do método chamador.

Exemplo:

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

Informação extra para resposta de Firas Assaad.

Eu usei new StackFrame(1).GetMethod().Name;no .net core 2.1 com injeção de dependência e estou recebendo o método de chamada como 'Iniciar'.

Eu tentei com [System.Runtime.CompilerServices.CallerMemberName] string callerName = "" e isso me dá o método de chamada correto

cdev
fonte
-1
var callingMethod = new StackFrame(1, true).GetMethod();
string source = callingMethod.ReflectedType.FullName + ": " + callingMethod.Name;
Mauro Sala
fonte
1
Eu não reduzi o voto, mas gostaria de observar que a adição de algum texto para explicar por que você postou informações muito semelhantes (anos depois) pode aumentar o valor da pergunta e evitar novas reduções de voto.
Shaun Wilson