Utilizando Reflexão em C# para Inspeção Dinâmica de Tipos

A Reflexão (Reflection) é uma funcionalidade poderosa do framwork .NET que permite a um programa inspecionar e manipular informações de tipos em tempo de execução. Através da reflexão, é possível criar objetos dinamicamente, invocar métodos, e acessar propriedades e campos de forma programática.

A reflexão fornece objetos que encapsulam assemblies, módulos e tipos (geralmente do tipo Type). Ela possibilita a criação dinâmica de instâncias de um tipo, vincular um tipo a um objeto existente, ou obter o tipo de um objeto para então invocar seus métodos ou acessar seus campos e propriedades. Se o código utilizar attributes, a reflexão também permite acessá-los.

Distinção entre Arquivos EXE e DLL

No sistema operacional Windows, os formatos EXE e DLL possuem propósitos e características distintas.

  • EXE (Executável): Um arquivo de aplicação endependente que pode ser iniciado diretamente pelo sistema operacional.
  • DLL (Biblioteca de Vínculo Dinâmico): Contém código e dados compartilháveis por múltiplos programas, sendo carregada em tempo de execução.

A principal diferença estrutural reside no ponto de entrada: um EXE contém um método Main, enquanto uma DLL não. Em termos de desempenho, DLLs podem ser compartilhadas na memória entre processos, economizando recursos.

Metadados em C# e .NET

Os metadados são informações armazenadas em um assembly (arquivo EXE ou DLL) que descrevem seus tipos, membros, atributos e dependências. Eles são fundamentais para funcionalidades como a reflexão, serialização, debugging e para o funcionamento de ferramentas de desenvolvimento.

Criando uma DLL

Para criar uma DLL em um ambiente de desenvolvimento como o Visual Studio, o processo geral é:

  1. Criar uma nova solução e adicionar um projeto do tipo Class Library.
  2. Desenvolver as classes, interfaces e outros tipos necessários dentro deste projeto.
  3. Compilar o projeto (Build ou Rebuild). O compilador gerará um arquivo .dll na pasta bin\Debug ou bin\Release do projeto.

Principais Usos da Reflexão

  1. Carregamento Dinâmico de Assemblies: Usar a classe Assembly para carregar bibliotecas e extrair informações de seus tipos.
  2. Criação e Manipulação de Objetos: Utilizar Activator.CreateInstance para instanciar objetos com base em informações de tipo obtidas em runtime.
  3. Acesso a Membros Privados: Embora desencorajado, é possível acessar campos e métodos privados de uma classe.
  4. Introspecção de Tipo: Obter informações sobre as propriedades, campos, métodos e construtores de uma classe.
  5. Invocação Dinâmica de Métodos: Chamar métodos sem conhecê-los em tempo de compilação.
  6. Leitura e Escrita de Propriedades: Acessar dinamicamente os valores das propriedades de um objeto.

Exemplos Práticos de Reflexão

Carregando um Assembly

Existem diferentes métodos para carregar um assembly, cada um com seu caso de uso:

Assembly.Load

Carrega um assembly pelo seu nome, procurando no diretório base do aplicativo ou no GAC (Global Assembly Cache).

Assembly meuAssembly = Assembly.Load("MinhaBiblioteca");

Assembly.LoadFrom

Carrega um assembly a partir de um caminho de arquivo específico (absoluto ou relativo).

Assembly meuAssembly = Assembly.LoadFrom(@"C:\Projetos\libs\MinhaBiblioteca.dll");

Assembly.LoadFile

Carrega um assemb com base em um caminho de arquivo, sem resolver automaticamente suas dependências.

Assembly meuAssembly = Assembly.LoadFile(@"C:\Projetos\libs\MinhaBiblioteca.dll");

Carregando a partir de um Byte Array

Este método permite carregar um assembly diretamente de uma matriz de bytes, útil em cenários como o carregamento de assemblies compilados em memória.

byte[] bytesDoAssembly = File.ReadAllBytes(@"C:\Projetos\libs\MinhaBiblioteca.dll");
Assembly meuAssembly = AppDomain.CurrentDomain.Load(bytesDoAssembly);

Enumerando Tipos em um Assembly

Após carregar um assembly, podemos iterar sobre todos os tipos (classes, structs, etc.) definidos nele.

byte[] bytesDoAssembly = File.ReadAllBytes(@"C:\Projetos\libs\MinhaBiblioteca.dll");
Assembly assemblyCarregado = AppDomain.CurrentDomain.Load(bytesDoAssembly);

foreach (Type tipo in assemblyCarregado.GetTypes())
{
    Console.WriteLine(tipo.Name);
}
// Saída exemplo:
// Funcionario
// Gerente
// RepositorioGenerico`1

Criando uma Instância de um Objeto

Construtor sem Parâmetros

Considere a classe abaixo, que possui um construtor padrão sem parâmetros.

public class Funcionario
{
    public string NomeCompleto { get; set; }
    public int Idade { get; set; }

    public Funcionario()
    {
        NomeCompleto = "Não definido";
        Idade = 0;
    }

    public void ExibirDados()
    {
        Console.WriteLine($"Nome: {NomeCompleto}, Idade: {Idade}");
    }
}

Criando uma instância usando reflexão:

Type tipoFuncionario = typeof(Funcionario);
object instanciaFuncionario = Activator.CreateInstance(tipoFuncionario);

Construtor com Parâmetros

Se a classe definir apenas construtores com parâmetros, o Activator.CreateInstance precisa receber esses argumentos.

public class Funcionario
{
    public string NomeCompleto { get; set; }
    public int Idade { get; set; }

    public Funcionario(string nome, int idade)
    {
        NomeCompleto = nome;
        Idade = idade;
    }

    public void ExibirDados()
    {
        Console.WriteLine($"Nome: {NomeCompleto}, Idade: {Idade}");
    }
}
// ...
Type tipoFunc = typeof(Funcionario);
object instFunc = Activator.CreateInstance(tipoFunc, "Maria Silva", 28);

Manipulando Propriedades via Reflexão

Podemos ler e escrever valores de propriedades de forma dinâmica.

// Partindo de uma instância já criada (instFunc)
Type tipo = instFunc.GetType();

// Alterando o valor da propriedade 'NomeCompleto'
PropertyInfo propNome = tipo.GetProperty("NomeCompleto");
propNome.SetValue(instFunc, "Ana Pereira");

// Lendo o valor da propriedade
string nomeAtual = (string)propNome.GetValue(instFunc);
Console.WriteLine(nomeAtual); // Saída: Ana Pereira

// Alternativa usando dynamic
Console.WriteLine(((dynamic)instFunc).NomeCompleto);

Introspecção e Invocação de Métodos

Listando Métodos e Parâmetros

Type tipoFunc = typeof(Funcionario);

foreach (MethodInfo metodo in tipoFunc.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly))
{
    Console.WriteLine($"Método: {metodo.Name}");
    foreach (ParameterInfo parametro in metodo.GetParameters())
    {
        Console.WriteLine($"  - Parâmetro: {parametro.Name} (Tipo: {parametro.ParameterType.Name})");
    }
}
// Saída exemplo:
// Método: ExibirDados
//   - (Sem parâmetros para este método específico)

Invocando um Método de Instância

// Assumindo que 'instFunc' já foi criado
MethodInfo metodoExibir = tipoFunc.GetMethod("ExibirDados");
metodoExibir.Invoke(instFunc, null); // Chama instFunc.ExibirDados()

Invocando um Método Estático

Para métodos estáticos, passamos null como instância.

public class Calculadora
{
    public static int Somar(int a, int b) => a + b;
}

// ...
Type tipoCalc = typeof(Calculadora);
MethodInfo metodoSomar = tipoCalc.GetMethod("Somar");
object[] argumentos = new object[] { 10, 5 };
int resultado = (int)metodoSomar.Invoke(null, argumentos); // resultado = 15

Trabalhando com Tipos Genéricos

Criando uma Instância de uma Classe Genérica

É necessário primeiro construir o tipo genérico concreto.

public class Repositorio<t>
{
    public T Entidade { get; set; }

    public Repositorio(T entidade)
    {
        Entidade = entidade;
    }
}

// ...
Type tipoGenericoAberto = typeof(Repositorio<>);
Type tipoGenericoConcreto = tipoGenericoAberto.MakeGenericType(typeof(Funcionario));
object repositorio = Activator.CreateInstance(tipoGenericoConcreto, instFunc); // instFunc é uma instância de Funcionario</t>

Invocando um Método Genérico

Para métodos genéricos, precisamos especificar os argumentos de tipo.

public class Utilidades
{
    public static void ExibirTipos<t1 t2="">(T1 valor1, T2 valor2)
    {
        Console.WriteLine(valor1);
        Console.WriteLine(valor2);
    }
}

// ...
Type tipoUtil = typeof(Utilidades);
MethodInfo metodoGenerico = tipoUtil.GetMethod("ExibirTipos");
MethodInfo metodoGenericoConcreto = metodoGenerico.MakeGenericMethod(typeof(string), typeof(int));
metodoGenericoConcreto.Invoke(null, new object[] { "Olá", 123 });</t1>

Considerações Importantes

  • Desempenho: A reflexão é significativamente mais lenta que o acesso direto a tipos. Seu uso deve ser ponderado, especialmente em código crítico para performance.
  • Encapsulamento: Acessar membros privados via reflexão quebra o encapsulamento do objeto, devendo ser evitado a menos que seja estritamente necessário.
  • Segurança: A reflexão pode contornar as verificações de tipo feitas em tempo de compilação, introduzindo potenciais riscos de segurança.

Tags: CSharp dotnet reflection type-inspection dynamic-loading

Publicado em 6-5 02:19 por Thomas