Desenvolvimento - C#

Consultando e invocando métodos dincamicamente usando Reflection

Em programação orientada a objetos, para invocarmos um método de um objeto, só precisamos criar a instância do objeto e, em seguida, invocar um dos métodos que já conhecemos do objeto. Imagine você acessar um objeto de forma genérica, ou seja, você não conhece nada sobre os métodos e propriedades do objeto...

por Renato Guimarães



Em programação orientada a objetos, para invocarmos um método de um objeto, só precisamos criar a instância do objeto e, em seguida, invocar um dos métodos que já conhecemos do objeto. Imagine você acessar um objeto de forma genérica, ou seja, você não conhece nada sobre os métodos e propriedades do objeto. As propriedades e métodos do objeto só serão conhecidos somente quando for preciso. Para chamarmos um método de qualquer objeto sem termos um conhecimento prévio, devemos pesquisar todos os métodos do objeto, encontrar o método desejado, e invocá-lo em tempo de execução. Quer saber como isso é possível? Somente através de Reflection.

Para que seja possível essa pesquisa no objeto, um objeto Java ou C# tem metadados ou informações sobre seus dados, tais como campos, seus métodos, seus construtores e interfaces que o objeto implementa. Reflection é a capacidade de consultar e usar esses metadados. Instanciar um objeto e chamar seu método ou propriedade diretamente é muito mais rápido do que fazer isso através de Reflection.

Reflection tem alguns usos importantes:

  • Visualizar e consultar metadados. Muitas IDEs usam reflection em seus objetos para categorizar seus métodos e campos numa forma gráfica, como uma árvore.
  • Invocação dinâmica. Isto permite você ler e atribuir campos de um objeto em tempo de execução e invocar métodos daquele objeto de forma genérica. Uma grande parte das ferramentas de mapeamento objeto/relacional definem mapeamentos dos campos e métodos do objeto a colunas de uma tabela num banco de dados. Essas ferramentas usam invocação dinâmica para chamar os métodos apropriados para atribuir e ler campos de um objeto após ter sido carregado do banco de dados. Quem usa Java perceberá que essa técnica também é usada em algumas tags JSP.
  • Criar tipos personalizados. Programadores Java já estão acostumados com essas duas formas de usar reflection, mas vão ficar surpresos em saber que o C# permite você programaticamente acessar e modificar a IL e criar um novo tipo ou classe. Isso poderia ser equivalente a duas APIs do Java: uma que permite acessar byte code programaticamente, e outra para criar classes e interfaces. O jdk 1.3.1 não tem uma API para acessar byte code.

Nesse artigo de hoje vamos estudar algumas classes do namespace System.Reflection e, ao mesmo tempo, construir exemplos para consultar métodos, campos, construtores, atribuir valores a campos e propriedades, etc de um tipo específico dinamicamente.

As principais classes para Reflection

As principais classes para reflection em C# estão no namespace System.Reflection. O equivalente em Java pode ser encontrado nos pacotes java.lang.reflect e java.lang. System.Type é a principal classe para fazer toda descoberta dos metadados do objeto. Sendo assim, antes de fazer qualquer coisa, precisamos obter o Type do tipo que desejamos fazer Reflection. Para isso, podemos utilizar ou o operador typeof ou o métod GetType da classe System.Type. A classe System.Type é grosseiramente equivalente a classe java.lang.Class. Portanto, existe uma diferença. Em Java, qualquer coisa é um java.lang.Object, então java.lang.Class é o metadado para todos objetos. Em C#, System.Type pode representar qualquer um dos seguintes tipos: classes, value types, arrays, pointers, interfaces, e enumerators. O operador typeof do C# é equivalente ao operador .class do java. Para obter metadados da classe System.String, e verificar se ela é uma interface, você faz o seguinte:

	Console.WriteLine(typeof(System.String).isInterface); 

O similar em java seria:

	System.out.println(System.class.isInterface()); 

Listagem 1: Recupera informações da classe System.Type através de reflection. Para recuperar informações de qualquer classe, só precisa mudar a linha Type tipo = typeof(System.Type). Por exemplo, para explorar a classe System.String: tipo = typeof(System.String);

using System;
using System.Reflection;

class ExlporaType
{
	public static void Main(string[] args){
	//Obtém o objeto Type da classe System.Type. Poderia ser qualquer classe.
		Type tipo = typeof(System.Type);

		//Verifica se é uma classe abstrata. Caso não, imprime todos os 
		//seus construtores. Uma classe abstrata não pode ser instanciada.
		//Sendo assim, não possui construtores.
		if (!tipo.IsAbstract)
		{
			Console.WriteLine("Type tem os seguintes construtores:");
			ImprimeConstrutores(tipo);
		}

		//Imprime todos os métodos
		ImprimeMetodos(tipo);

		//Imprime todas  as propriedades
		ImprimePropriedades(tipo);

		//Imprime todos os campos
		ImprimeCampos(tipo);

		//Imprime as interfaces que o objeto implementa
		ImprimeInterfaces(tipo);

		//Imprime os atributos
		ImprimeAtributos(tipo);

		Console.ReadLine();
	}


	//Método para imprimir todos os construtores da classe
	private static void ImprimeConstrutores(Type pTipo)
	{
		Console.WriteLine("  $$$$$ Imprimindo os construtores da classe System.Type");
		Console.WriteLine(" ");

		ConstructorInfo[] construtores = pTipo.GetConstructors();

		Console.WriteLine("Existe(m) " + construtores.Length + " construtores. ");
		Console.WriteLine(" ");
		foreach (ConstructorInfo construtor in construtores){
			Console.WriteLine("Nome = " + construtor.Name);

			ParameterInfo[] parametros = construtor.GetParameters();

			if (parametros.Length> 0)
				Console.WriteLine(" ----- Parâmetros do construtor ---- ");

			foreach (ParameterInfo parametro in parametros){
				Console.WriteLine("Posição = " + parametro.Position);
				Console.WriteLine("Nome = " + parametro.Name);
				Console.WriteLine("Tipo = " + parametro.GetType());
				Console.WriteLine(" ");
			}

			Console.WriteLine("=======================");
			Console.WriteLine(" ");
		}
	}


	//Método para imprimir todos os campos da classe. 
	private static void ImprimeCampos(Type pTipo){
		Console.WriteLine("  $$$$$ Imprimindo os campos da classe System.type");
		Console.WriteLine(" ");

		FieldInfo[] campos = pTipo.GetFields();

		Console.WriteLine("Existe(m) " + campos.Length + " campos. ");
		Console.WriteLine(" ");
		foreach (FieldInfo campo in campos)
		{
			Console.WriteLine("Nome campo = " + campo.Name);
			Console.WriteLine("Tipo =  " + campo.GetType());
			Console.WriteLine("É serializado = " + !campo.IsNotSerialized);
			Console.WriteLine("É privado =  " + campo.IsPrivate);
			Console.WriteLine("É estático =  " + campo.IsStatic);
			Console.WriteLine(" ");
		}
	}


	//Método para imprimir todas as propriedades da classe. 
	private static void ImprimePropriedades(Type pTipo)
	{
		Console.WriteLine("  $$$$$ Imprimindo as propriedades da classe System.type");
		Console.WriteLine(" ");

		//Obtém as propriedades através do método GetProperties   
		PropertyInfo[] props = pTipo.GetProperties();

		Console.WriteLine("Existe(m) " + props.Length + "propriedades. ");
		Console.WriteLine(" ");
		foreach (PropertyInfo propriedade in props)
		{
			Console.WriteLine("Nome = " + propriedade.Name);
			Console.WriteLine("Tipo =  " + propriedade.GetType());
			Console.WriteLine("Leitura = " + propriedade.CanRead);
			Console.WriteLine("Escrita =  " + propriedade.CanWrite);
			Console.WriteLine(" ");
		}
	}


	//Método para imprimir todas as interfaces que o objeto implementa
	private static void ImprimeInterfaces(Type pTipo){
		Console.WriteLine("  $$$$$ Imprimindo as interfaces implementadas pela classe " & _ 
			"System.Type");
		Console.WriteLine(" ");

		//Obtém as interfaces através do método GetInterfaces    
		Type[] interfaces = pTipo.GetInterfaces();

		Console.WriteLine("Existe(m) " + interfaces.Length + " interfaces implementadas. ");
		Console.WriteLine(" ");
		foreach (Type auxiliar in interfaces)
		{
			Console.WriteLine("Nome = " + auxiliar.FullName);
		}

		Console.WriteLine(" ");
	}


	//Método para imprimir os atributos da classe
	private static void ImprimeAtributos(Type pTipo)
	{
		Console.WriteLine("  $$$$$ Imprimindo os atributos da classe System.type");
		Console.WriteLine(" ");

		Object[] atributos = pTipo.GetCustomAttributes(true);

		Console.WriteLine("Existe(m) " + atributos.Length + " atributos. ");
		Console.WriteLine(" ");
		foreach (Object atributo in atributos)
		{
			Console.WriteLine("Tipo = " + atributo.GetType());
		}

		Console.WriteLine(" ");
	}


	//Método para imprimir os métodos da classe
	private static void ImprimeMetodos(Type pTipo)
	{
		Console.WriteLine("  $$$$$ Imprimindo os métodos da classe System.Type");
		Console.WriteLine(" ");

		MethodInfo[] metodos = pTipo.GetMethods();

		Console.WriteLine("Existe(m) " + metodos.Length + " métodos. ");
		Console.WriteLine("");
		foreach (MethodInfo metodo in metodos)
		{
			Console.WriteLine("Nome = " + metodo.Name);
			Console.WriteLine("É privado =  " + metodo.IsPrivate);
			Console.WriteLine("É estático = " + metodo.IsStatic);

			ParameterInfo[] parametros = metodo.GetParameters();

			if (parametros.Length> 0)
				Console.WriteLine(" ----- Parâmetros do método ---- ");

			foreach (ParameterInfo parametro in parametros)
			{
				Console.WriteLine("Posição = " + parametro.Position);
				Console.WriteLine("Nome = " + parametro.Name);
				Console.WriteLine("Tipo = " + parametro.GetType());
				Console.WriteLine("É de entrada = " + parametro.IsIn);
				Console.WriteLine("É de saída = " + parametro.IsOut);
				Console.WriteLine(" ");
			}

			Console.WriteLine("=======================");
			Console.WriteLine(" ");
		}
	}
}
Perceba que a classe System.Type é abstrata, ou seja, não tem construtor. Usamos o operador typeof para obter o tipo associado com a classe System.Class. Na listagem 1 as classes ConstructorInfo, PropertyInfo, FieldInfo, MethodInfo e ParameterInfo todas herdam da classe System.Reflection.MemberInfo. Como a saída da listagem um é grande, vou listar somente o resumo das quantidades de cada tipo:
  • Número de construtores = 0
  • Número de métodos = 145
  • Número de propriedades = 57
  • Número de campos = 6
  • Número de interfaces = 4
  • Número de atributos = 4

Navegando e consultando os membros(campos, métodos, propriedades) de uma classe

Como vimos no resumo da listagem 1, 124 métodos já é uma quantidade grande para se fazer uma pesquisa por um método com a assinatura desejada. Diferente do java, C# possui uma API com métodos para selecionar e pesquisar, baseado num certo critério, por um conjunto de membros. Como critério, normalmente, você especifica modificadores de acesso que precedem a definição dos membros, por exemplo: static, public, e métodos privados, campos, e construtores.

Na listagem 2 vamos implementar o código necessário para navegar e consultar campos e métodos de qualquer tipo do C#. O método FindMembers da classe MemberInfo pode ser usado para direcionar a pesquisa. Os parâmetros desse método são MemberTypes, BindingFlags, MemberFilter, e Object. Antes de tudo, vamos entender o significado desses parâmetros em detalhes:

  • MemberTypes é um objeto que indica o tipo do membro a ser pesquisado. Isso inclui All, Constructor, Custom, Event, Field, Method, Nestedtype, Property, e TypeInfo. Também podemos usar MemberTypes.Method para pesquisar um método. BindingFlags é um tipo enumerado (enum) que controla a forma que a pesquisa é conduzida. Alguns deles são: IgnoreCase, Instance, Public, Static. O membro padrão de BindingFlags indica nenhum desses valores, no qual é o que vamos querer porque não queremos esse tipo de restrição.
  • MemberFilter é um delegate que é usado para filtrar a lista dos membros no array de objetos da classe MemberInfo. O filtro que usaremos é Type.FilterName, um campo da classe Type usado para filtrar o nome.
  • Object é um valor string que será usado pelo filtro. Nesse caso nós passamos "Get*" para comparar somente os métodos que começam pelo nome Get.

Listagem 2: Navega e consulta os campos e métodos de um Type.

using System;
using System.Reflection;

class PesquisaCampoMetodo
{
	public static void Main(string[] args)
	{
		//Obtém o Type através do método GetType
		Type tipo = Type.GetType(args[0]);

		//Consulta os campos
		ConsultarCampos(tipo);

		//Consulta os métodos
		ConsultarMetodos(tipo);

		Console.ReadLine();
	}


	//Método para consultar os campos do tipo
	public static void ConsultarCampos(Type pTipo){
    //Recupera os campos do tipo
    FieldInfo[] campos = pTipo.GetFields();
    Listar("Campos do tipo", campos);

    //Campos sem modificador de acesso, ou seja, sem public, private etc 
    campos = pTipo.GetFields(BindingFlags.Default);
    Listar("Campos sem modificador de acesso", campos);

    //Consulta todos os campos públicos e de instância
    campos = pTipo.GetFields(BindingFlags.Public | BindingFlags.Instance);
    Listar("Campos publicos e de instancia", campos);   

    //Consulta todos os campos não públicos e de instância
    campos = pTipo.GetFields(BindingFlags.Instance | BindingFlags.NonPublic);
    Listar("Campos NAO publicos e de instancia", campos);   

    //Consulta todos os campos públicos e estáticos
    campos = pTipo.GetFields(BindingFlags.Static | BindingFlags.Public);
    Listar("Campos estaticos e publicos", campos);   

    //Consulta todos os campos Não públicos e estáticos
    campos = pTipo.GetFields(BindingFlags.Static | BindingFlags.NonPublic);
    Listar("Campos estaticos e NAO publicos", campos);   

    //Consulta todos os campos declarados somente públicos
    campos = pTipo.GetFields(BindingFlags.DeclaredOnly | BindingFlags.Public);
    Listar("Campos somente declarados publicos", campos);   

    //Consulta todos os campos
    MemberInfo[] membros = pTipo.FindMembers(
		MemberTypes.Field, BindingFlags.Default, Type.FilterName, "Get*");
    Listar("Campos encontrados na pesquisa", membros);
  }


	//Método para consultar os métodos do tipo
	public static void ConsultarMetodos(Type pTipo){
    //Recupera os métodos do tipo
    MethodInfo[] metodos = pTipo.GetMethods();
    Listar("Metodos do tipo", metodos);

    //Métodos sem modificar de acesso, ou seja, sem public, private etc 
    metodos = pTipo.GetMethods(BindingFlags.Default);
    Listar("Metodos sem modificador de acesso", metodos);

    //Consulta todos os métodos públicos e de instância
    metodos = pTipo.GetMethods(BindingFlags.Public | BindingFlags.Instance);
    Listar("Metodos publicos e de instancia", metodos);   

    //Consulta todos os métodos não públicos e de instância
    metodos = pTipo.GetMethods(BindingFlags.Instance | BindingFlags.NonPublic);
    Listar("Metodos NAO públicos e de instancia", metodos);   

    //Consulta todos os métodos públicos e estáticos
    metodos = pTipo.GetMethods(BindingFlags.Static | BindingFlags.Public);
    Listar("Metodos estaticos e publicos", metodos);   

    //Consulta todos os métodos Não públicos e estáticos
    metodos = pTipo.GetMethods (BindingFlags.Static | BindingFlags.NonPublic);
    Listar("Metodos estaticos e NAO publicos", metodos);   

    //Consulta todos os métodos declarados somente públicos
    metodos = pTipo.GetMethods (BindingFlags.DeclaredOnly | BindingFlags.Public);
    Listar("Metodos somente declarados publicos", metodos);   

    //Consulta todos os métodos
    MemberInfo[] membros = pTipo.FindMembers(
		MemberTypes.Method, BindingFlags.Default, Type.FilterName, "Get*");
    Listar("Metodos encontrados na pesquisa", membros);
  }

    //Método para listar os membros recuperados 
    public static void Listar(String texto, MemberInfo[] membros){
      Console.WriteLine("-------------");
      Console.WriteLine(texto);
      Console.WriteLine("-------------");
      foreach(MemberInfo membro in membros){
        Console.WriteLine("Nome = " + membro.Name);  
    } 
  }
}
Compile a classe PesquisaCampoMetodo e execute-a com a seguinte linha de comando:

c:\>PesquisaCampoMetodo System.Type

Um trecho da saída do bloco da listagem 2 será:

-------------
Campos do tipo
-------------
Nome = FilterAttribute
Nome = FilterName
Nome = FilterNameIgnoreCase
Nome = Missing
Nome = Delimiter
Nome = EmptyTypes
...
-------------
Campos estaticos e NAO publicos
-------------
Nome = defaultBinder
Nome = valueType
Nome = enumType
Nome = objectType
Nome = DefaultLookup
....

Na listagem 2 usamos a classe MemberInfo (super classe de FieldInfo e MethodInfo) para lidar com campos e métodos porque precisamos acessar somente o nome e o tipo do membro. Essas duas propriedades são herdadas pelas da classe MemberInfo. Além disso, vimos que carregamos o array de membros de duas formas: chamando o get do tipo específico(GetFields ou GetMethods) ou chamando o método FindMembers.

Em algumas situações você poderá precisar chamar um método específico. A listagem 3 mostra como encontrar um método específico através do método GetMethod. A mesma chamada pode ser feita para recuperar um campo, propriedade ou construtor específico de um tipo. Usando reflection para obter um determinado tipo de um membro requer que você já conheça o membro que está procurando naquele tipo. Caso contrário, ocorrerá uma exceção se o membro não for encontrado.

Listagem 3: Obtém um método específico de um Type. Usaremos a classe System.String porque a chamada deve ser feita a membros que já conhecemos, por exemplo, o método ToUpper.

using System;
using System.Reflection;

class MetodoEspecifico{

	public static void Main(string[] args){
		//Obtendo o Type da classe System.String
		Type tipo = typeof(System.String);

		ObterMetodo(tipo);
		Console.ReadLine();
	}

     //Método para obter um método específico da classe Type
     public static void ObterMetodo(Type pTipo){
    //Obtém o método usando somente o nome. No caso do método ToUpper
    //será levantada uma exceção porque o método é sobrecarregado. 
    //Sendo assim, a chamada fica confusa para identificar qual versão
    //do método você quer executar.
    MethodInfo metodo = null; 

    //Trata o erro que será levantando
    try{
      metodo = pTipo.GetMethod("ToUpper");
    }catch(Exception ex){
      Console.WriteLine(ex.Message);
    }

    //Obtém o método usando somente o nome. O nome é case-sensitive,
    //ou seja, o nome "Maria" é diferente de "maria". Deve ser informado 
    //o nome exato do método e, além disso, o método não deve ser sobre-
    //carregado.
    metodo = pTipo.GetMethod("StartsWith");
	Console.WriteLine("Método StartsWith padrão " + metodo.Name);

    //Obtém um método sobrecarregado usando o nome e os tipos de
    //parâmetros que são necessários para chamá-lo.
    Type[] parametros = new Type[]{typeof(int), typeof(char)};	
	metodo = pTipo.GetMethod("PadLeft", parametros);
    Console.WriteLine("Metodo PadLeft com parametros " + metodo.Name);

    //Obtém um método sem parâmetros
    Type[] semParams = new Type[0];	
    metodo = pTipo.GetMethod("ToUpper", semParams);
    Console.WriteLine("Metodo ToUpper sem parametros " + metodo.Name);

    //Obtém um método sem parâmetros. Caso "método" seja null é porque
    //não foi encontrado o método
	metodo = pTipo.GetMethod("StartsWith", semParams);
	Console.WriteLine("Metodo StartsWith sem parametros: " + (metodo == null));
  }
}

A saída da listagem 3 será:

Ambiguous match found.
Método StartsWith padrao StartsWith
Metodo PadLeft com parametros PadLeft
Metodo ToUpper sem parametros ToUpper
Metodo StartsWith sem parametros: True

Veja que, mesmo sabendo que o método StartsWith requer um parâmetro, estamos tentando chamar um método StartsWith que não requer parâmetros.

//Obtém um método sem parâmetros. Caso "método" seja null é porque
//não foi encontrado o método
metodo = pTipo.GetMethod("StartsWith", semParams);
Console.WriteLine("Método StartsWith sem parâmetros: " + (metodo == null) );

O método StartsWith sem parâmetros não existe na classe System.String, e, ao invés de lançar uma exceção, é retornado um null.

Perceba que os métodos de pesquisa de membros - tais como GetMethod(nome), GetConstructor(nome), GetMember(nome), GetField(nome), e GetProperties - não lançam uma exceção da classe NoSuchMemberException ou alguma de suas subclasses; ao contrário, retornam um objeto null se o membro não foi encontrado.

Invocando métodos e atribuindo valores a campos e propriedades

Até agora nós vimos como pesquisar, consultar, e retornar um membro ou uma coleção de membros de um tipo específico. O real poder de Reflection entra em campo quando você invoca métodos e construtores e atribui valores a propriedades e campos. A listagem 4 mostra como podemos invocar alguns métodos da classe System.String.

Listagem 4: InvocA dinamicamente alguns métodos e propriedades da classe System.String

using System;
using System.Reflection;

class InvocandoMetodos{
	
	public static void Main(string[] args){
	  //Obtém o Type da classe System.String
	  Type tipo = typeof(System.String);

	  //Invoca o construtor
	  ChamarConstrutor(tipo);

	  //Invoca o método Remove
	  ChamarMetodo(tipo);

	  //Obtém o valor de uma propriedade
	  ObterPropriedade(tipo);

	  Console.ReadLine();
	}

	//Método para invocar o construtor que aceita um array de char
	public static void ChamarConstrutor(Type pTipo){
      //A chamada que vamos fazer é semelhante a String s = new String(char[] c);
      ConstructorInfo construtor = null;

      //Obtém o construtor que aceita um array de char como parâmetro. Para isso, 
      //precisamos informar os tipos dos parâmetros  no método GetConstructor
      construtor = pTipo.GetConstructor(new Type[]{typeof(Char[])});

      //Monta o array de char que será o valor do argumento 
      Char[] arrayValor = new Char[] {"D", "O", "T", "N", "E", "T"};
  
      //Cria o array de argumentos para chamada do construtor. Os parâmetros 
      // devem ser passados num array de object. Como o construtor que queremos só
      // exige um parâmetro que é um array de char, criamos  array com um elemento.
      Object[] argumentos = new Object[]{arrayValor};
      try{
        Console.WriteLine(construtor.Invoke(argumentos));
      }catch(Exception ex){
        Console.WriteLine(ex.StackTrace);
      }
    }

	//Método para invocar o método Remove
	public static void ChamarMetodo(Type pTipo){
      MethodInfo metodo = null;

      //String que o valor será removido
      String destino = "SharpShooters.NET Recife-PE";

	  //Monta array com os tipos dos parâmetros para não dar erro de ambiguidade.
	  //Isso porque o método Remove é sobrecarregado.
	  Type[] tiposParams = new Type[] { typeof(int), typeof(char) };	

      //Obtém o método através de GetMethod
      metodo = pTipo.GetMethod("Remove", tiposParams);

      //Monta array com os valores dos parâmetros
      Object[] argumentos = new Object[]{17, 10};
      Console.WriteLine(metodo.Invoke(destino, argumentos));    
    }

	//Método para obter o valor de uma propriedade
	public static void ObterPropriedade(Type pTipo){
      PropertyInfo propriedade = null;

      //String que o valor será removido
      String argumento = "Células Acadêmicas .NET. Você já conhece?";

      //Obtém a propriedade através do método GetProperty
      propriedade = pTipo.GetProperty("Length");

      //Invoca o método
      Console.WriteLine(propriedade.GetValue(argumento, null));    
  }
}

A saída da listagem 4 será:

DOTNET
SharpShooters.NET
41

Com os exemplos acima você aprendeu a utilizar as principais classes do namespace System.Reflection. Nos exemplos vimos como pesquisar e invocar métodos, construtores, campos e propriedades. Usamos só algumas características da API. Além disso, podemos gerar e compilar uma classe dinamicamente. Esse será o assunto do próximo artigo... Gerando código IL dinamicamente.

Renato Guimarães

Renato Guimarães - Bacharel em Sistemas de Informação e trabalha com tecnologia da informação há mais de 15 anos.