¿Puedes usar la reflexión para encontrar el nombre del método que se está ejecutando actualmente?

202

Como dice el título: ¿Puede la reflexión darle el nombre del método que se está ejecutando actualmente?

Me inclino a adivinar que no, debido al problema de Heisenberg. ¿Cómo se llama a un método que le dirá el método actual sin cambiar cuál es el método actual? Pero espero que alguien pueda probar que estoy equivocado allí.

Actualizar:

  • Parte 2: ¿Podría usarse esto para buscar también dentro del código de una propiedad?
  • Parte 3: ¿Cómo sería la actuación?

Resultado final
Aprendí sobre MethodBase.GetCurrentMethod (). También aprendí que no solo puedo crear un seguimiento de pila, sino que también puedo crear el marco exacto que necesito si lo deseo.

Para usar esto dentro de una propiedad, simplemente tome una .Substring (4) para eliminar 'set_' o 'get_'.

Joel Coehoorn
fuente
Joel, sé que es una pregunta antigua, pero ¿qué quieres decir con crear el marco exacto de un método?
Abhijeet
Se refiere a un elemento específico en la pila de llamadas: la parte del seguimiento de la pila que importa.
Joel Coehoorn

Respuestas:

119

A partir de .NET 4.5 también puede usar [CallerMemberName]

Ejemplo: un establecedor de propiedades (para responder a la parte 2):

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

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

El compilador proporcionará literales de cadena coincidentes en los sitios de llamadas, por lo que básicamente no hay sobrecarga de rendimiento.

John Nilsson
fuente
3
¡Esto es genial! Estaba usando el StackFrame(1)método descrito en otras respuestas para el registro, que parecía funcionar hasta que Jitter decidió comenzar a incluir cosas. No quería agregar el atributo para evitar la alineación por razones de rendimiento. El uso del [CallerMemberName]enfoque solucionó el problema. ¡Gracias!
Brian Rogers
55
[CallerMemberName] está disponible en la red 4 con la compilación BCL empaquetada
Venson
2
Tenga en cuenta que usar StackFrame (1) en modo de depuración debería funcionar. Pero al usar el modo de lanzamiento al compilar, puede haber algunas optimizaciones y la pila puede no ser lo que espera.
Axel O'Connell
¿Esto no devolverá el miembro llamante (es decir, SomeProperty), en lugar del método que se está ejecutando actualmente?
Lennart
1
Sí, invocar al setter resultará en una llamada OnPropertyChanged("SomeProperty")y noOnPropertyChanged("SetProperty")
John Nilsson
189

Para los no asyncmétodos, uno puede usar

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

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

Recuerde que para los asyncmétodos devolverá "MoveNext".

Ed Guiness
fuente
8
Tenga en cuenta que esto no siempre produce los resultados esperados. Es decir, los pequeños métodos o propiedades a menudo están integrados en las versiones de lanzamiento, en cuyo caso el resultado será el nombre del método del llamante.
Abel
55
Que yo sepa, no. Porque en tiempo de ejecución, el MSIL ya no está disponible desde el puntero de ejecución (está JITted). Todavía puede usar la reflexión si conoce el nombre del método. El punto es, cuando está en línea, el método que se está ejecutando actualmente es ahora otro método (es decir, uno o más más arriba en la pila). En otras palabras, el método desapareció. Incluso si marca su método con NoInlining, todavía existe la posibilidad de que se optimice la llamada de cola, en cuyo caso también se ha ido. Sin embargo, funcionará mientras esté en la compilación de depuración.
Abel
1
Para evitar en línea, agregue el atributo [MethodImpl (MethodImplOptions.NoInlining)] en la parte superior del método.
alex.peter
Dentro del asyncmétodo probablemente obtendrá "MoveNext" como nombre del método.
Victor Yarema
46

El fragmento proporcionado por Lex fue un poco largo, así que estoy señalando la parte importante ya que nadie más usó exactamente la misma técnica:

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

Esto debería devolver resultados idénticos a la técnica MethodBase.GetCurrentMethod (). Name , pero aún vale la pena señalarlo porque podría implementar esto una vez en su propio método usando el índice 1 para el método anterior y llamarlo desde varias propiedades diferentes. Además, solo devuelve un marco en lugar de todo el seguimiento de la pila:

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

También es de una sola línea;)

Joel Coehoorn
fuente
puede ser una cadena pública estática GetPropertyName () en una clase auxiliar? método estático?
Kiquenet
2
Lo mismo que con la respuesta de Ed Guiness: la pila puede ser diferente en las versiones de lanzamiento y el primer método puede no ser el mismo que el método actual en los casos de optimización en línea o de cola.
Abel
Vea la respuesta de John Nilsson para una buena manera de solucionar el problema de la inlining si está utilizando .Net 4.5.
Brian Rogers
esto podría ser mejor que la respuesta aceptada y la respuesta anterior también
T.Todua
16

Pruebe esto dentro del método Main en un programa de consola vacío:

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

Salida de consola:
Main

Lars Mæhlum
fuente
12

Sí definitivamente.

Si desea manipular un objeto, en realidad uso una función como esta:

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

Esta línea:

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

Recorre el marco de la pila para encontrar el método de llamada y luego usamos la reflexión para obtener los valores de información de parámetros que se le pasan para una función genérica de informe de errores. Para obtener el método actual, simplemente use el marco de pila actual (1) en su lugar.

Como otros han dicho para el nombre de los métodos actuales, también puede usar:

MethodBase.GetCurrentMethod()

Prefiero caminar por la pila porque si miras internamente ese método, simplemente crea un StackCrawlMark de todos modos. Dirigirme directamente a la Pila me parece más claro

Post 4.5 ahora puede usar [CallerMemberNameAttribute] como parte de los parámetros del método para obtener una cadena del nombre del método; esto puede ayudar en algunos escenarios (pero realmente, por ejemplo, en el ejemplo anterior)

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

Esto parecía ser principalmente una solución para el soporte INotifyPropertyChanged donde anteriormente había cadenas sucias a lo largo de su código de evento.

Lex
fuente
No tonto; Simplemente los pasé. Probablemente podría hacer algo para que sea más simple de ver, pero el esfuerzo por la relación de recompensa parecía favorecer mantenerlo simple. Esencialmente, el desarrollador solo copia en la lista de parámetros de la firma del método (eliminando los tipos, por supuesto).
Lex
¿Qué es: ExceptionFormat y GetParameterList?
Kiquenet
Muy tarde en la respuesta, pero: ExceptionFormat es un formato de cadena constante y GetParameterList es una función simple que formatea los parámetros con los valores (puede hacer esto en línea)
Lex
11

Comparación de formas de obtener el nombre del método: utilizando una construcción de temporización arbitraria en LinqPad:

CÓDIGO

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

RESULTADOS

reflejo reflejo

stacktrace stacktrace

inlineconstant inlineconstant

constante constante

expr e => e.expr ()

exprmember exprmember

miembro principal

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

Tenga en cuenta que los métodos expry callermemberno son del todo "correctos". Y allí verá una repetición de un comentario relacionado de que la reflexión es ~ 15 veces más rápida que stacktrace.

drzaus
fuente
9

EDITAR: MethodBase es probablemente una mejor manera de obtener el método en el que se encuentra (en lugar de toda la pila de llamadas). Sin embargo, todavía estaría preocupado por la línea.

Puede usar un StackTrace dentro del método:

StackTrace st = new StackTrace(true);

Y la mirada a los cuadros:

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

Sin embargo, tenga en cuenta que si el método está en línea, no estará dentro del método que cree que está. Puede usar un atributo para evitar la alineación:

[MethodImpl(MethodImplOptions.NoInlining)]
denis phillips
fuente
Inline debido a la optimización de Release es especialmente complicado ya que el código se comportará de manera diferente en las configuraciones de Debug y Release. Cuidado con las pequeñas propiedades, son las víctimas más probables de esto.
DK.
Me pregunto por qué usar en new StackTrace(true)lugar de new StackTrace(false). Establecer eso truehará que el seguimiento de la pila intente capturar el nombre del archivo, el número de línea, etc., lo que podría hacer que esta llamada sea más lenta. De lo contrario, una buena respuesta
Ivaylo Slavov
6

La forma simple de tratar es:

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

Si System.Reflection está incluido en el bloque using:

MethodBase.GetCurrentMethod().DeclaringType.FullName + "." + MethodBase.GetCurrentMethod().Name;
Tiburón
fuente
4

¿Qué tal esto 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
0

Prueba esto...

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

Acabo de hacer esto con una clase estática simple:

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

luego en tu código:

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