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ãesPara 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.