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

202

Como o título diz: A reflexão pode fornecer o nome do método atualmente em execução.

Estou inclinado a adivinhar que não, por causa do problema de Heisenberg. Como você chama um método que informa o método atual sem alterar qual é o método atual? Mas espero que alguém possa me provar que estou errado lá.

Atualizar:

  • Parte 2: isso também poderia ser usado para procurar dentro de código uma propriedade?
  • Parte 3: Como seria o desempenho?

Resultado final
Aprendi sobre MethodBase.GetCurrentMethod (). Aprendi também que não apenas posso criar um rastreamento de pilha, como também posso criar apenas o quadro exato de que preciso, se quiser.

Para usar isso dentro de uma propriedade, basta usar um .Substring (4) para remover o 'set_' ou 'get_'.

Joel Coehoorn
fonte
Joel, eu sei que é uma pergunta antiga, mas o que você quer dizer com criar a estrutura exata de um método?
Abhijeet 26/09
Refere-se a um item específico na pilha de chamadas: a parte do rastreamento de pilha que importa.
Joel Coehoorn 26/09/13

Respostas:

119

A partir do .NET 4.5, você também pode usar [CallerMemberName]

Exemplo: um configurador de propriedades (para responder à parte 2):

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

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

O compilador fornecerá literais de seqüência de caracteres correspondentes nos locais de chamada, portanto, basicamente não há sobrecarga de desempenho.

John Nilsson
fonte
3
Isso é ótimo! Eu estava usando o StackFrame(1)método descrito em outras respostas para o log, que parecia funcionar até que o Jitter decidiu começar a alinhar as coisas. Eu não queria adicionar o atributo para impedir a inclusão por motivos de desempenho. O uso da [CallerMemberName]abordagem corrigiu o problema. Obrigado!
Brian Rogers
5
[CallerMemberName] está disponível na rede 4 com a compilação BCL empacotada
Venson
2
Leve em consideração que o uso do StackFrame (1) no modo de depuração deve funcionar. Porém, ao usar o modo de liberação ao compilar, pode haver algumas otimizações e a pilha pode não ser o que você espera.
Axel O'Connell
Isso não retornará o membro que está chamando (por exemplo, SomeProperty), em vez do método atualmente em execução?
Lennart
1
Sim, invocando o setter irá resultar em uma chamada para OnPropertyChanged("SomeProperty")e nãoOnPropertyChanged("SetProperty")
John Nilsson
189

Para não asyncmétodos, pode-se usar

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

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

Por favor, lembre-se que para os asyncmétodos ele retornará "MoveNext".

Ed Guiness
fonte
8
Esteja ciente de que isso nem sempre gera os resultados esperados. Ou seja, pequenos métodos ou propriedades são frequentemente incorporados em compilações de release. Nesse caso, o resultado será o nome do método do chamador.
Abel
5
Tanto quanto eu sei, não. Como no tempo de execução, o MSIL não está mais disponível no ponteiro de execução (é JITted). Você ainda pode usar a reflexão se souber o nome do método. O ponto é que, quando incorporado, o método atualmente em execução agora é outro método (ou seja, um ou mais acima da pilha). Em outras palavras, o método desapareceu. Mesmo se você marcar seu método com NoInlining, ainda há uma chance de que ele seja otimizado para chamadas finais, caso em que também foi desativado. No entanto, ele funcionará enquanto estiver na compilação de depuração.
Abel
1
Para evitar inline, adicione o atributo [MethodImpl (MethodImplOptions.NoInlining)] na parte superior do método.
alex.peter
Dentro do asyncmétodo, você provavelmente obterá "MoveNext" como nome do método.
Victor Yarema 17/01
46

O snippet fornecido por Lex foi um pouco longo, então estou apontando a parte importante, pois ninguém mais usou exatamente a mesma técnica:

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

Isso deve retornar resultados idênticos à técnica MethodBase.GetCurrentMethod (). Name , mas ainda vale a pena salientar, porque eu poderia implementar isso uma vez em seu próprio método usando o índice 1 para o método anterior e chamá-lo de várias propriedades diferentes. Além disso, ele retorna apenas um quadro em vez de todo o rastreamento da pilha:

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

É um one-liner, também;)

Joel Coehoorn
fonte
pode ser pública, estática, GetPropertyName () em uma classe auxiliar? método estático?
Kiquenet
2
O mesmo da resposta de Ed Guiness: a pilha pode ser diferente nas versões do release e o primeiro método pode não ser o mesmo do método atual nos casos de otimização de chamada embutida ou final.
Abel
Veja a resposta de John Nilsson para uma boa maneira de contornar a questão em questão, se você estiver usando o .Net 4.5.
Brian Rogers
esta poderia ser melhor do que resposta aceita e resposta acima demasiado
T.Todua
16

Tente isso dentro do método Main em um programa de console vazio:

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

Saída do console:
Main

Lars Mæhlum
fonte
12

Sim definitivamente.

Se você deseja manipular um objeto, eu realmente uso uma função 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;
}

Está linha:

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

Percorre o quadro da pilha para encontrar o método de chamada e usamos a reflexão para obter os valores das informações dos parâmetros passados ​​a ele para uma função genérica de relatório de erros. Para obter o método atual, basta usar o quadro de pilha atual (1).

Como já foi dito para o nome dos métodos atuais, você também pode usar:

MethodBase.GetCurrentMethod()

Eu prefiro andar na pilha porque se olhar internamente para esse método, ele simplesmente cria um StackCrawlMark de qualquer maneira. Dirigir-se à pilha diretamente parece mais claro para mim

Na publicação 4.5, agora você pode usar o [CallerMemberNameAttribute] como parte dos parâmetros do método para obter uma sequência do nome do método - isso pode ajudar em alguns cenários (mas na verdade, digamos o exemplo acima)

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

Isso parecia ser principalmente uma solução para o suporte do INotifyPropertyChanged, onde anteriormente você tinha strings espalhadas por todo o código do evento.

Lex
fonte
Não é bobo; Eu simplesmente os passei. Você provavelmente poderia fazer algo para tornar mais simples a aparência, mas o esforço para recompensar a taxa parecia favorecer a manutenção. Essencialmente, o desenvolvedor apenas copia na lista de parâmetros da assinatura do método (removendo tipos de curso).
Lex
o que é: ExceptionFormat e GetParameterList?
Kiquenet
Way no final da resposta, mas: ExceptionFormat é um formato de string constante e GetParameterList é uma função simplesmente que formata os parâmetros com os valores (você poderia fazer isso em linha)
Lex
11

Comparando maneiras de obter o nome do método - usando uma construção de temporização arbitrária no 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

reflexão reflexão

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

Observe que os métodos expre callermembernão estão "certos". E aí você vê uma repetição de um comentário relacionado de que a reflexão é ~ 15x mais rápida que o rastreamento de pilha.

drzaus
fonte
9

Edição: MethodBase é provavelmente uma maneira melhor de apenas obter o método em que você está (em oposição a toda a pilha de chamadas). Eu ainda estaria preocupado em incluir no entanto.

Você pode usar um StackTrace dentro do método:

StackTrace st = new StackTrace(true);

E o olhar para os quadros:

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

No entanto, esteja ciente de que, se o método estiver embutido, você não estará dentro do método que pensa estar. Você pode usar um atributo para evitar inlining:

[MethodImpl(MethodImplOptions.NoInlining)]
Denis Phillips
fonte
Inline devido à otimização da versão é especialmente complicado, pois o código se comportará de maneira diferente nas configurações de depuração e versão. Cuidado com as pequenas propriedades, elas são as vítimas mais prováveis ​​disso.
DK.
Eu me pergunto por que você usa em new StackTrace(true)vez de new StackTrace(false). Definir isso para trueque o rastreamento da pilha tente capturar o nome do arquivo, o número da linha e etc., o que pode tornar a chamada mais lenta. Caso contrário, uma resposta agradável
Ivaylo Slavov
6

A maneira simples de lidar é:

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

Se o System.Reflection estiver incluído no bloco using:

MethodBase.GetCurrentMethod().DeclaringType.FullName + "." + MethodBase.GetCurrentMethod().Name;
Tubarão
fonte
4

Que tal isso:

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
0

Tente isso ...

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

Eu fiz isso com uma classe estática simples:

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

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