¿Cómo puedo encontrar el método que llamó al método actual?

503

Al iniciar sesión en C #, ¿cómo puedo saber el nombre del método que llamó al método actual? Lo sé todo System.Reflection.MethodBase.GetCurrentMethod(), pero quiero ir un paso por debajo de esto en la traza de la pila. He considerado analizar el seguimiento de la pila, pero espero encontrar una forma más explícita y limpia, algo así como Assembly.GetCallingAssembly()métodos.

duda
fuente
22
Si está utilizando .net 4.5 beta +, puede usar la API de información de llamadas .
Rohit Sharma
55
La información de la persona que llama también es mucho más rápida
paloma el
44
Creé una rápida BenchmarkDotNet de referencia de los tres métodos principales ( StackTrace, StackFramey CallerMemberName) y publicado los resultados como una esencia para que otros puedan ver aquí: gist.github.com/wilson0x4d/7b30c3913e74adf4ad99b09163a57a1f
Shaun Wilson

Respuestas:

513

Prueba esto:

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

un trazador de líneas:

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

Es de Obtener método de llamada usando Reflection [C #] .

Firas Assaad
fuente
12
También puede crear solo el marco que necesita, en lugar de la pila completa:
Joel Coehoorn
188
nuevo StackFrame (1) .GetMethod (). Name;
Joel Coehoorn el
12
Sin embargo, esto no es del todo confiable. ¡A ver si esto funciona en un comentario! Pruebe lo siguiente en una aplicación de consola y verá que las optimizaciones del compilador la rompen. vacío estático Main (string [] args) {CallIt (); } vacío privado estático 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 01 de
10
Esto no funciona cuando el compilador se alinea o la cola optimiza el método, en cuyo caso la pila se contrae y encontrará otros valores de los esperados. Sin embargo, cuando solo use esto en versiones de depuración, funcionará bien.
Abel
46
Lo que he hecho en el pasado es agregar el atributo del compilador [MethodImplAttribute (MethodImplOptions.NoInlining)] antes del método que buscará el seguimiento de la pila. Eso asegura que el compilador no alineará el método, y el seguimiento de la pila contendrá el método de llamada verdadero (no estoy preocupado por la recursión de cola en la mayoría de los casos)
Jordan Rieger
363

En C # 5 puede obtener esa información utilizando la información de la persona que llama :

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

También puede obtener el [CallerFilePath]y [CallerLineNumber].

Coincoin
fuente
13
Hola, no es C # 5, está disponible en 4.5.
AFract
35
Las versiones de @AFract Language (C #) no son las mismas que la versión .NET.
kwesolowski
66
@stuartd Parece que [CallerTypeName]se eliminó de .Net framework actual (4.6.2) y Core CLR
Ph0en1x
44
@ Ph0en1x nunca estuvo en el marco, mi punto era que sería útil si lo fuera, por ejemplo, cómo obtener el nombre de tipo de un miembro que llama
stuartd
3
@DiegoDeberdt: he leído que usar esto no tiene inconvenientes ya que hace todo el trabajo en tiempo de compilación. Creo que es exacto en cuanto a lo que llamó el método.
cchamberlain
109

Puede usar la información de la persona que llama y los parámetros opcionales:

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

Esta prueba ilustra esto:

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

Si bien el StackTrace funciona bastante rápido arriba y no sería un problema de rendimiento en la mayoría de los casos, la información de la persona que llama es mucho más rápida aún. En una muestra de 1000 iteraciones, lo cronometré como 40 veces más rápido.

paloma
fuente
Sin embargo
DerApe
1
Tenga en cuenta que esto no funciona si la persona que llama pasa un grupo: CachingHelpers.WhoseThere("wrong name!");==> "wrong name!"porque CallerMemberNamesolo sustituye el valor predeterminado.
Olivier Jacot-Descombes
@ OlivierJacot-Descombes no funciona de la misma manera que un método de extensión no funcionaría si le pasaras un parámetro. podría usar otro parámetro de cadena que podría usarse. También tenga en cuenta que resharper le dará una advertencia si intenta pasar una discusión como lo hizo.
paloma
1
@dove puede pasar cualquier thisparámetro explícito a un método de extensión. Además, Olivier es correcto, puede pasar un valor y [CallerMemberName]no se aplica; en su lugar, funciona como una anulación donde normalmente se usaría el valor predeterminado. De hecho, si observamos la IL, podemos ver que el método resultante no es diferente de lo que normalmente se habría emitido para un [opt]argumento, CallerMemberNamepor lo tanto , la inyección de es un comportamiento CLR. Por último, los documentos: "Los atributos de Información de llamadas afectan [...] al valor predeterminado que se pasa cuando se omite el argumento "
Shaun Wilson
2
Esto es perfecto y asyncamigable que StackFrameno te ayudará. Tampoco afecta ser llamado desde una lambda.
Aaron
65

Una recapitulación rápida de los 2 enfoques con la comparación de velocidad como parte importante.

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

Determinación de la persona que llama en tiempo de compilación

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

Determinar la persona que llama usando la pila

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

Comparación de los 2 enfoques

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

Como puede ver, ¡usar los atributos es mucho, mucho más rápido! Casi 25 veces más rápido de hecho.

Tikall
fuente
Este método parece ser un enfoque superior. También funciona en Xamarin sin un problema de espacios de nombres que no están disponibles.
lyndon hughey
63

Podemos mejorar el código del Sr. Assad (la respuesta aceptada actual) solo un poco creando instancias solo el marco que realmente necesitamos en lugar de toda la pila:

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

Esto podría funcionar un poco mejor, aunque con toda probabilidad todavía tiene que usar la pila completa para crear ese marco único. Además, todavía tiene las mismas advertencias que Alex Lyman señaló (el optimizador / código nativo podría corromper los resultados). Por último, es posible que desee comprobar para asegurarse de que new StackFrame(1)o .GetFrame(1)no regresan null, por improbable que esa posibilidad podría parecer.

Vea esta pregunta relacionada: ¿Puede usar la reflexión para encontrar el nombre del método que se está ejecutando actualmente?

Joel Coehoorn
fuente
1
¿Es posible que sea new ClassName(…)igual a nulo?
Nombre para mostrar
1
Lo bueno es que esto también funciona en .NET Standard 2.0.
fechado el
60

En general, puede usar la System.Diagnostics.StackTraceclase para obtener un System.Diagnostics.StackFrame, y luego usar el GetMethod()método para obtener un System.Reflection.MethodBaseobjeto. Sin embargo, hay algunas advertencias sobre este enfoque:

  1. Representa la pila de tiempo de ejecución : las optimizaciones podrían incorporar un método, y no verá ese método en el seguimiento de la pila.
  2. Será no muestra ningún marcos nativos, así que si hay incluso una posibilidad de que su método está siendo llamado por un método nativo, esto será no trabajo, y no hay en realidad ninguna manera actualmente disponible para hacerlo.

( NOTA: solo estoy ampliando la respuesta proporcionada por Firas Assad ).

Alex Lyman
fuente
2
En modo de depuración con optimizaciones desactivadas, ¿podría ver cuál es el método en el seguimiento de la pila?
AttackingHobo
1
@AttackingHobo: Sí, a menos que el método esté en línea (optimizaciones activadas) o un marco nativo, lo verá.
Alex Lyman
38

A partir de .NET 4.5, puede usar los atributos de información de llamadas :

  • CallerFilePath - El archivo fuente que llamó a la función;
  • CallerLineNumber - Línea de código que llamó a la función;
  • CallerMemberName - Miembro que llamó a la función.

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

 

Esta instalación también está presente en ".NET Core" y ".NET Standard".

Referencias

  1. Microsoft - Información de la persona que llama (C #)
  2. Microsoft - CallerFilePathAttributeClase
  3. Microsoft - CallerLineNumberAttributeClase
  4. Microsoft - CallerMemberNameAttributeClase
Ivan Pinto
fuente
15

Tenga en cuenta que hacerlo no será confiable en el código de lanzamiento, debido a la optimización. Además, ejecutar la aplicación en modo sandbox (recurso compartido de red) no le permitirá tomar el marco de la pila.

Considere la programación orientada a aspectos (AOP), como PostSharp , que en lugar de ser llamado desde su código, modifica su código y, por lo tanto, sabe dónde está en todo momento.

Lasse V. Karlsen
fuente
Tienes toda la razón de que esto no funcionará en el lanzamiento. No estoy seguro de que me guste la idea de la inyección de código, pero supongo que, en cierto sentido, una declaración de depuración requiere una modificación del código, pero aún así. ¿Por qué no simplemente volver a las macros C? Al menos es algo que puedes ver.
ebyrob
9

Obviamente, esta es una respuesta tardía, pero tengo una mejor opción si puede usar .NET 4.5 o más:

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

Esto imprimirá la fecha y hora actuales, seguido de "Namespace.ClassName.MethodName" y terminando con ": text".
Salida de muestra:

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

Uso de la muestra:

Logger.WriteInformation<MainWindow>("MainWindow initialized");
Camilo Terevinto
fuente
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;
}
Flandes
fuente
Vaya, debería haber explicado el parámetro "Método después" un poco mejor. Entonces, si está llamando a este método en una función de tipo "log", querrá obtener el método justo después de la función "log". entonces llamarías a GetCallingMethod ("log"). -Felices
Flandes
6

Quizás estés buscando algo como esto:

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

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

Una clase fantástica está aquí: http://www.csharp411.com/c-get-calling-method/

Tebo
fuente
StackFrame no es confiable. Subir "2 cuadros" podría volver fácilmente también a las llamadas de método.
usuario2864740
2

Otro enfoque que he usado es agregar un parámetro al método en cuestión. Por ejemplo, en lugar de void Foo()usar void Foo(string context). Luego pase una cadena única que indique el contexto de la llamada.

Si solo necesita el llamador / contexto para el desarrollo, puede eliminarlo paramantes del envío.

GregUzelac
fuente
2

Para obtener el nombre del método y el nombre de la clase, intente esto:

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

será suficiente, creo.

caner
fuente
1

Eche un vistazo al nombre del método de registro en .NET . Tenga cuidado de usarlo en el código de producción. StackFrame puede no ser confiable ...

Yuval pelado
fuente
55
Un resumen del contenido sería bueno.
Peter Mortensen
1

También podemos usar lambda para encontrar a la persona que llama.

Supongamos que tiene un método definido por usted:

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

y quieres encontrar a quien llama.

1 . Cambie la firma del método para que tengamos un parámetro de tipo Acción (Func también funcionará):

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

2 . Los nombres lambda no se generan al azar. La regla parece ser:> <CallerMethodName> __X donde CallerMethodName se reemplaza por la función anterior y X es un índice.

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

3 . Cuando llamamos al Método A, el parámetro llamador debe generar el parámetro Acción / Func. Ejemplo:

MethodA(() => {});

4 . Dentro del Método A ahora podemos llamar a la función auxiliar definida anteriormente y encontrar el Método de Información del método del llamador.

Ejemplo:

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

Información adicional a la respuesta de Firas Assaad.

Lo he usado new StackFrame(1).GetMethod().Name;en .net core 2.1 con inyección de dependencia y recibo un método de llamada como 'Inicio'.

Lo intenté [System.Runtime.CompilerServices.CallerMemberName] string callerName = "" y me da el método de llamada correcto

cdev
fuente
-1
var callingMethod = new StackFrame(1, true).GetMethod();
string source = callingMethod.ReflectedType.FullName + ": " + callingMethod.Name;
Mauro Sala
fuente
1
No voté en contra, pero quería señalar que agregar un texto para explicar por qué publicó información muy similar (años más tarde) puede aumentar el valor de la pregunta y evitar más votaciones negativas.
Shaun Wilson