Como uso a reflexão para invocar um método privado?

326

Há um grupo de métodos particulares na minha classe e preciso chamar um dinamicamente com base em um valor de entrada. O código de chamada e os métodos de destino estão na mesma instância. O código fica assim:

MethodInfo dynMethod = this.GetType().GetMethod("Draw_" + itemType);
dynMethod.Invoke(this, new object[] { methodParams });

Nesse caso, GetMethod()não retornará métodos privados. O BindingFlagsque eu preciso fornecer para GetMethod()que ele possa localizar métodos particulares?

Jeromy Irvine
fonte

Respostas:

498

Simplesmente altere seu código para usar a versãoGetMethod sobrecarregada que aceita BindingFlags:

MethodInfo dynMethod = this.GetType().GetMethod("Draw_" + itemType, 
    BindingFlags.NonPublic | BindingFlags.Instance);
dynMethod.Invoke(this, new object[] { methodParams });

Aqui está a documentação de enumeração BindingFlags .

wprl
fonte
248
Vou me meter em tantos problemas com isso.
24330 Frank Schwieterman
1
BindingFlags.NonPublicnão retornando privatemétodo .. :(
Moumit 22/10
4
@MoumitMondal são seus métodos estáticos? Você precisa especificar BindingFlags.Instance, bem como BindingFlags.NonPublicmétodos não estáticos.
precisa saber é
Não @BrianS .. o método é non-statice privatea classe é herdada de System.Web.UI.Page.. isso me faz de idiota de qualquer maneira .. eu não encontrei o motivo .. :(
Moumit
3
A adição BindingFlags.FlattenHierarchypermitirá que você obtenha métodos das classes pai para sua instância.
Dragonthoughts 22/08/19
67

BindingFlags.NonPublicnão retornará nenhum resultado por si só. Como se vê, combiná-lo com BindingFlags.Instancefaz o truque.

MethodInfo dynMethod = this.GetType().GetMethod("Draw_" + itemType, 
    BindingFlags.NonPublic | BindingFlags.Instance);
Jeromy Irvine
fonte
Mesma lógica se aplica a internalfunções bem
supertopi
Eu tenho um problema similar. E se "this" for uma classe filho e você tentar invocar o método privado dos pais?
persianLife
É possível usar isso para chamar métodos protegidos da classe base.base?
Shiv
51

E se você realmente quer ter problemas, facilite a execução escrevendo um método de extensão:

static class AccessExtensions
{
    public static object call(this object o, string methodName, params object[] args)
    {
        var mi = o.GetType ().GetMethod (methodName, System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance );
        if (mi != null) {
            return mi.Invoke (o, args);
        }
        return null;
    }
}

E uso:

    class Counter
    {
        public int count { get; private set; }
        void incr(int value) { count += value; }
    }

    [Test]
    public void making_questionable_life_choices()
    {
        Counter c = new Counter ();
        c.call ("incr", 2);             // "incr" is private !
        c.call ("incr", 3);
        Assert.AreEqual (5, c.count);
    }
cod3monk3y
fonte
14
Perigoso? Sim. Mas uma grande extensão auxiliar quando envolvida no meu namespace de teste de unidade. Obrigado por isso.
Robert Wahler
5
Se você se preocupa com exceções reais lançadas a partir do método chamado, é uma boa ideia envolvê-lo em try catch block e lançar novamente a exceção interna quando TargetInvokationException capturado. Eu faço isso na minha extensão auxiliar de teste de unidade.
Slobodan Savkovic
2
Reflexão perigosa? Hmmm ... C #, Java, Python ... na verdade tudo é perigoso, até mesmo o mundo: D Você apenas tem que ter cuidado sobre como fazê-lo de forma segura ...
Legends
16

A Microsoft modificou recentemente a API de reflexão, tornando a maioria dessas respostas obsoletas. O seguinte deve funcionar em plataformas modernas (incluindo Xamarin.Forms e UWP):

obj.GetType().GetTypeInfo().GetDeclaredMethod("MethodName").Invoke(obj, yourArgsHere);

Ou como um método de extensão:

public static object InvokeMethod<T>(this T obj, string methodName, params object[] args)
{
    var type = typeof(T);
    var method = type.GetTypeInfo().GetDeclaredMethod(methodName);
    return method.Invoke(obj, args);
}

Nota:

  • Se o método desejado está em uma superclasse objdo Tgenérico deve ser explicitamente definido para o tipo de superclasse.

  • Se o método for assíncrono, você pode usar await (Task) obj.InvokeMethod(…).

Owen James
fonte
Ele não funciona para a versão UWP .net pelo menos porque funciona apenas para métodos públicos : " Retorna uma coleção que contém todos os métodos públicos declarados no tipo atual que correspondem ao nome especificado ".
Dmytro Bondarenko,
1
@DmytroBondarenko Eu testei contra métodos privados e funcionou. Eu vi isso, no entanto. Não sei por que ele se comporta de maneira diferente da documentação, mas pelo menos funciona.
Owen James
Sim, eu não chamaria todas as outras respostas obsoletas, se a documentação GetDeclareMethod()indicar que se destina a ser usado para recuperar apenas um método público.
Dot Dot Net
10

Você tem certeza absoluta de que isso não pode ser feito por herança? A reflexão é a última coisa que você deve considerar ao resolver um problema, pois torna mais difícil a refatoração, a compreensão do seu código e qualquer análise automatizada.

Parece que você deveria ter apenas uma classe DrawItem1, DrawItem2, etc, que substitui seu dynMethod.

Bill K
fonte
1
@ Bill K: Dadas outras circunstâncias, decidimos não usar herança para isso, daí o uso de reflexão. Na maioria dos casos, faríamos dessa maneira.
Jeromy Irvine
8

A reflexão especialmente sobre membros privados está errada

  • A reflexão quebra o tipo de segurança. Você pode tentar invocar um método que não existe (mais), ou com os parâmetros errados, ou com muitos parâmetros, ou insuficientes ... ou até na ordem errada (este é o meu favorito :)). A propósito, o tipo de retorno também pode mudar.
  • A reflexão é lenta.

A reflexão de membros privados quebra o princípio do encapsulamento e, portanto, expõe seu código ao seguinte:

  • Aumente a complexidade do seu código, pois ele precisa lidar com o comportamento interno das classes. O que está oculto deve permanecer oculto.
  • Facilita a quebra do seu código, pois ele será compilado, mas não será executado se o método mudar de nome.
  • Facilita a quebra do código privado, porque, se for privado, não se destina a ser chamado dessa maneira. Talvez o método privado espere algum estado interno antes de ser chamado.

E se eu devo fazê-lo de qualquer maneira?

Há casos em que, quando você depende de terceiros ou precisa de uma API não exposta, é necessário refletir um pouco. Alguns também o usam para testar algumas classes que possuem, mas não desejam alterar a interface para dar acesso aos membros internos apenas para testes.

Se você fizer, faça certo

  • Mitigue o fácil de quebrar:

Para atenuar o problema fácil de quebrar, o melhor é detectar qualquer possível quebra testando em testes de unidade que seriam executados em uma construção de integração contínua ou algo assim. Obviamente, isso significa que você sempre usa a mesma montagem (que contém os membros privados). Se você usa uma carga e reflexão dinâmicas, gosta de brincar com fogo, mas sempre pode capturar a exceção que a chamada pode produzir.

  • Mitigue a lentidão da reflexão:

Nas versões recentes do .Net Framework, o CreateDelegate supera um fator 50 que o MethodInfo chama:

// The following should be done once since this does some reflection
var method = this.GetType().GetMethod("Draw_" + itemType, 
  BindingFlags.NonPublic | BindingFlags.Instance);

// Here we create a Func that targets the instance of type which has the 
// Draw_ItemType method
var draw = (Func<TInput, Output[]>)_method.CreateDelegate(
                 typeof(Func<TInput, TOutput[]>), this);

drawas chamadas serão 50 vezes mais rápidas do que as MethodInfo.Invoke usadas drawcomo padrão Func:

var res = draw(methodParams);

Verifique esta publicação minha para ver referências em diferentes invocações de métodos

Fab
fonte
1
Embora eu receba injeção de dependência deve ser uma maneira preferida de teste de unidade, acho que usar com cautela não é totalmente ruim para usar a reflexão para acessar unidades que, de outra forma, seriam inacessíveis para teste. Pessoalmente, acho que, além dos modificadores [público] [protegido] [privado] normais, também devemos ter modificadores [Teste] [Composição] para que seja possível ter certas coisas visíveis durante esses estágios sem ser forçado a tornar tudo público ( e, portanto, tem que documentar totalmente esses métodos)
Andrew patê
1
Obrigado Fab por listar os problemas de refletir sobre membros privados, isso me fez rever como me sinto em usá-lo e chegou a uma conclusão ... Usar a reflexão para testar a unidade de seus membros privados está errado, mas deixar os caminhos de código não testados é realmente muito errado.
Andrew patê
2
Mas quando se trata de unidade de teste de código legado geralmente não-unidade testável, é brilhante maneira de fazê-lo
TS
2

Você não poderia apenas ter um método Draw diferente para cada tipo que deseja desenhar? Em seguida, chame o método Draw sobrecarregado que passa no objeto do tipo itemType a ser desenhado.

Sua pergunta não deixa claro se itemType se refere genuinamente a objetos de tipos diferentes.

Peter Hession
fonte
1

Eu acho que você pode passá-lo BindingFlags.NonPubliconde ele é o GetMethodmétodo.

Armin Ronacher
fonte
1

Invoca qualquer método, apesar de seu nível de proteção na instância do objeto. Aproveitar!

public static object InvokeMethod(object obj, string methodName, params object[] methodParams)
{
    var methodParamTypes = methodParams?.Select(p => p.GetType()).ToArray() ?? new Type[] { };
    var bindingFlags = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static;
    MethodInfo method = null;
    var type = obj.GetType();
    while (method == null && type != null)
    {
        method = type.GetMethod(methodName, bindingFlags, Type.DefaultBinder, methodParamTypes, null);
        type = type.BaseType;
    }

    return method?.Invoke(obj, methodParams);
}
Maksim Shamihulau
fonte
0

Leia esta resposta (complementar) (que às vezes é a resposta) para entender para onde está indo e por que algumas pessoas neste tópico reclamam que "ainda não está funcionando"

Eu escrevi exatamente o mesmo código que uma das respostas aqui . Mas eu ainda tinha um problema. Coloquei o ponto de interrupção em

var mi = o.GetType().GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Instance );

Executou mas mi == null

E continuou um comportamento como esse até que eu "reconstruí" todos os projetos envolvidos. Eu estava testando uma unidade enquanto o método de reflexão estava na terceira montagem. Era totalmente confuso, mas eu usei o Immediate Window para descobrir métodos e descobri que um método privado que eu tentei fazer no teste de unidade tinha nome antigo (eu o renomei). Isso me disse que a montagem antiga ou PDB ainda está lá, mesmo que o projeto de teste de unidade seja compilado - por alguma razão, o projeto que ele testou não foi construído. "reconstruir" funcionou

TS
fonte
-1

BindingFlags.NonPublic

Khoth
fonte