Trabalhando com Reflection em C#

Figura 1: Trabalhando com Reflection em C#

A reflexão é algo interessante que o .Net fornece, com ela podemos escrever código o qual lê as informações do metadado dos objeto em tempo de execução. Essas informações são toda a estrutura existente na classe, portanto métodos, propriedades e até mesmo atributos de classes e métodos são visualizadas.

Para iniciar a captura de todas as informações da classe, devemos utilizar o namespace System.Reflection. Para visualizar as informações devemos primeiro capturar o Type do objeto, ele é responsável por encapsular todas as informações citadas e a fornece a possibilidade de manipula-las.

Vamos a um caso simples de reflexão de um objeto.

Listagem 1: Reflexão de ValeuType


static void Main(string[] args)
{
    int inteiro = 10;
    string texto = "DevMedia";
    float Flutuante = 10.
    System.Type Tipo = null;
    Tipo = inteiro.GetType();
    Console.WriteLine(Tipo.Name);
    Console.WriteLine(texto.GetType().Name);
    Console.WriteLine(Flutuante.GetType().Nam
    Console.Read();
}

O resultado é:

Resultado da Reflexão

Figura 2: Resultado da Reflexão

Note que retornou os tipos e não o nome ou valores da propriedade.

Usar reflexão não é só uma tarefa para descobrir tipos, existe a possibilidade de refletir métodos encapsulados em uma DLL e utilizar os métodos e propriedades em tempo de execução.

Antes de efetuar o load de uma DLL, a reflexão permite também a geração de novas instâncias de um tipo, utilizando a classe Activator. Isso é só um exemplo das muitas possibilidades do reflection.

Vamos refletir uma classe Humano para que possamos exemplificar todos os principais pontos da Reflection.

Listagem 2: Classe Humano


public class Humano
 {
     private string TipoSanguineo { get; set; }
     public int Idade { get; set; }
     public int Altura { get; set; }
     public Double Peso { get; set; }
     public string Nome { get; set; }
     public void Piscar()
     {
         Console.WriteLine("Piscar os olhos agora.");
     }
     public void Respirar()
     {
         Console.WriteLine("Repirar 1...2...3");
     }
     public void PensarAlgo(string pensamentos, DateTime quando)
     {
         Console.WriteLine("Estou pensando em : " + pensamentos + " pensei nisso agora : " + quando.ToShortTimeString());
     } 
    public void SentirFome()
     {
         Console.WriteLine("Estou ficando com fome. Hora do Lanche.");
     }
     private void CantarNoBanheiro()
     {
         Console.WriteLine("Bla ... Bla ... Bla ...");
     }    
} 

Para conhecer o assembly da classe Humano:

Listagem 3: Carregar o Assembly

var Assembly = typeof (Humano).Assembly;

Veja que o seguinte valor irá aparecer no objeto:

Terra, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null

Onde Terra é o nome do namespace que comporta a classe Humano, a versão 1.0 é a versão do assembly no momento da compilação.

O próximo passo é capturar o Type da classe, para ter acesso a todos os atributos relacionados a ela e poder manipulá-los.

Listagem 4: Capturar o Type da classe

Type humanoType = typeof(Humano);

Como é o do nosso conhecimento, a classe Humano pertence ao namespace Terra e fomos criteriosos ao escolher a classe Humano. Isto deve ser afirmado, pois podemos também definir uma classe especifica dentro de um namespace a ser refletida. Por exemplo:

Listagem 5: Capturar uma classe dentro do namespace

var animaisType = Type.GetType("Terra.Animais");

Porém vamos seguir com o primeiro exemplo.

Após ter o Type armazenado, vamos navegar por entre os métodos e propriedades da classe e capturar alguns valores da mesma.

Primeiramente vamos criar uma nova instância para que seja possível a manipulação dos dados do objeto.

Listagem 6: Activator auxiliando na geração de novas instâncias

object newHuman = Activator.CreateInstance(humanoType);

Esta linha equivale ao operador New.

Agora caso não queira trabalhar com novas instâncias de objetos e sim com um objeto já instanciado e passado para o método de reflexão, simplesmente informe o mesmo no método, assim o GetValue e o SetValue serão realizados dentro de um objeto do escopo.

Para capturar todas as propriedades públicas existentes utilizamos o método GetProperties.

Listagem 7: Usando o método GetProperties


PropertyInfo[] properties = humanoType.GetProperties();
  foreach (var propertyInfo in properties)
  {
      Console.WriteLine(propertyInfo.Name);
  }
  Console.Read(); 
Propriedades públicas da classe

Figura 3: Propriedades públicas da classe

Para capturar uma propriedade pública existente na classe, vamos utilizar o método GetProperty.

Listagem 8: Capturando uma propriedade com GetProperty


    PropertyInfo property = humanoType.GetProperty("Idade");
    Console.WriteLine(property.GetValue(newHuman, null));
    Console.Read();
    

Sabemos que o resultado é zero, pois estamos capturando valores de uma nova instância. Então para informar valores para o objeto utilizamos o SetValue.

Listagem 9: Atribuindo valores usando o SetValue


    PropertyInfo propertySet = humanoType.GetProperty("Idade");
    propertySet.SetValue(newHuman, 23, null);
    Console.WriteLine(propertySet.GetValue(newHuman, null));
    Console.Read();
    

Pronto, nosso Humano agora tem 23 anos de idade. Repare que devemos informar em qual objeto desejamos informar o valor, porém precisamos capturar a propriedade em que queremos que isso aconteça. Adendo, como tudo isso ocorre em tempo de execução, se preocupe em cada letra digitada, verifique se os tipos de valores são corretos, pois toda a exceção só será dada em tempo de execução.

A classe Humano possui diversos métodos que são essenciais para ela, como respirar, portanto vamos invocar todas as necessidades existente.

Listagem 10: Invocar um método público


    humanoType.InvokeMember("Respirar", BindingFlags.InvokeMethod | BindingFlags.Public | BindingFlags.Instance, null, newHuman, null);
    humanoType.InvokeMember("Piscar", BindingFlags.InvokeMethod | BindingFlags.Public | BindingFlags.Instance, null, newHuman, null);
    humanoType.InvokeMember("SentirFome", BindingFlags.InvokeMethod | BindingFlags.Public | BindingFlags.Instance, null, newHuman, null);
    

O resultado é:

Os métodos públicos executados

Figura 4: Os métodos públicos executados

Assim como acontece com as propriedades, métodos privados não são visualizados de imediato pela reflexão, entretanto é possível executá-los, utilizando os BindingFlags apropriados para isso.

BindingFlags

É um enumerador com diversas opções que servem como parâmetros para o momento da reflexão. Não é o foco do artigo, mas é importante conhecê-lo para usufruir melhor da reflexão, o link de documentação do mesmo está em Referências.

Listagem 11: Acessando métodos privados


humanoType.InvokeMember("CantarNoBanheiro", BindingFlags.InvokeMethod | BindingFlags.Instance | BindingFlags.NonPublic, null, newHuman, null);

Repare que foi utilizado o BindingFlags.NonPublic.

Vale ressaltar que é possível aceder métodos que possuem parâmetros de entrada, como é o caso do PensarAlgo, veja:

Listagem 12: Invocar o pensamento


humanoType.InvokeMember("PensarAlgo", BindingFlags.InvokeMethod | BindingFlags.Instance | BindingFlags.Public, null, newHuman, new object[] { "em viajar.", DateTime.Now });

Conclusão

A reflexão de modo bem cru é tudo o que fazemos, criamos instâncias usando o New, atribuímos valores pelo Set e recuperamos pelo Get. Chamamos os métodos apenas encontrando o nome, porém não acessamos de fora seus métodos privados.

Porém a reflexão não para por aqui, isso é só o começo do entendimento.

Podemos gerar, por exemplo, métodos com reflexão para realizar conversão de tipos, se tornando um método reutilizável. Assim como, a reflexão é utilizada para trabalhar com classes que possuem atributos, como o [Serializable] e com isso o poder na automatização aumenta.

Obrigado.


Referências