Este artigo fala sobre Reflection, uma técnica para observar e modificar a estrutura do código executável em tempo de execução. Explica a implementação no .NET, falando sobre Introspection, IL Emitting e ferramentas análogas, como Mono.Cecil e FxCop Introspection Model.
Para que serve
Em linguagens com tipagem estática, como C# e VB.NET, muitas vezes certas funcionalidades não são alcançáveis em tempo de compilação. Reflection consegue aumentar o dinamismo das linguagens por permitir a manipulação do código .NET após a compilação.
Em que situação o tema é útil
Reflection é bastante útil em situações onde é preciso conhecer e modificar o estado de um objeto dinamicamente. Perfeito para engines de serialização ou mecanismos de plugins. Além disso, a capacidade de IL Emitting permite que sejam criados métodos e classes em tempo de execução aumentando, em alguns casos, até performance do código.
Reflection
Reflection é a capacidade de um programa analisar e alterar sua estrutura ou o seu comportamento em tempo de execução. No .NET, temos suporte ao Reflection através das classes do namespace System.Reflection. Com elas, é possível obter informações sobre assemblies, classes e outras estruturas dinamicamente. Neste artigo, iremos mostrar como criar uma engine de serialização de arquivos com colunas pré-fixadas, usando reflection. Iremos também mostrar como resolver possíveis problemas de performance associados a essa técnica.
Você sabe o que acontece quando um programa é executado? No C# e em diversas outras linguagens, o código-fonte precisa ser compilado para poder ser executado. Isto é, ele precisa ser transformado em uma representação mais amigável à máquina. Enquanto está executando, os programas compilados ficam carregados na memória, assim como os dados que possuem. Código e dados compartilham o mesmo espaço na memória. Sendo assim, pergunta-se: seria possível manipular o código em tempo de execução tal qual é possível com os dados?
A resposta é: sim! Por muito tempo, análise e modificação do código em tempo de execução têm sido utilizadas pelos programadores para dar características dinâmicas em seus programas. Entretanto, essas técnicas envolviam manipulação de endereços de memória de forma crua, o que geralmente podia levar a bugs indecifráveis e a sérias falhas de segurança.
Conforme as linguagens foram evoluindo, mecanismos foram sendo desenvolvidos para representar o código executável de forma estruturada, permitindo que um programa pudesse mais facilmente analisar sua própria representação através de estruturas de alto nível, reduzindo muito os possíveis bugs. Esses mecanismos definem o que se conhece por Reflection (ou Reflexão). Para termos de nomenclatura, quando essa representação abrange o código executável, denomina-se reflexão computacional; quando abrange as estruturas definidas no programa (como classes e métodos), é chamada reflexão estrutural.
Como citado, o .NET suporta reflexão estrutural amplamente através do namespace System.Reflection e, de uma forma mais tímida, também suporta reflexão computacional através de IL Emitting. Esse suporte é padronizado na plataforma para que seja possível analisar a estrutura de um programa independente da linguagem na qual ele foi escrito. Isto é, os metadados de um programa escrito em VB.NET, por exemplo, podem ser lidos por um programa escrito em C#, de forma transparente. Isso é crucial para permitir a interoperabilidade das linguagens.
Reflection está presente em muitos pontos da BCL (Base Class Librarys) e em muitas bibliotecas e frameworks do mercado. ORMs como NHibernate e Entity Framework usam reflection para alterar o estado de objetos em tempo de execução. Bibliotecas como Castle.DynamicProxy e a System.Xml.Serialization usam IL Emitting para gerar código em tempo de execução, seja com o objetivo de aumentar a performance ao longo tempo, ou até mesmo para estender a funcionalidade de certos objetos.
Reflection ou Introspection?
Existe muita discussão com relação a nomenclatura no assunto. Algumas fontes dizem que Reflection é a capacidade de analisar e alterar o programa em tempo de execução, e que a análise somente é chamada de introspection. Outras fontes dizem que isso é irrelevante e que introspection, na verdade, tem a ver com a capacidade de um objeto conhecer seu próprio tipo dinamicamente. Será assumida a segunda definição para este artigo.
O .NET é capaz de introspection através do método GetType, presente na classe object e herdado por todos os outros objetos. Ele retorna uma instância da classe Type, porta de entrada para a maior parte das manipulações usando reflection. As estruturas principais do .NET estão representadas por classes específicas no namespace System.Reflection. A Figura 1 mostra algumas das principais classes desse namespace.
Figura 1. Hierarquia de classes que herdam de MemberInfo
Cada uma dessas classes permite obter informações sobre as estruturas de um programa. Uma instância de Type, por exemplo, possui o GetMethods, que é capaz de enumerar os métodos públicos de um certo tipo. A classe PropertyInfo permite que obtenha-se ou altere-se o valor de uma propriedade em um objeto dinamicamente. Exemplos práticos serão discutidos a frente.
IL Emitting
Outra técnica importante de reflection é o uso das classes do namespace System.Reflection.Emit. Com ele, o programa é capaz de gerar classes e assemblies em tempo de execução, opcionalmente salvando em disco. O conjunto de classes desse namespace foi feito especialmente para ser usado por compiladores e script engines, mas tem grande utilidade em aplicações e bibliotecas comuns, pois por MSIL ser a linguagem mais básica da plataforma .NET, é possível alcançar grande flexibilidade ao escrever o código.
IL é Intermediate Language, também conhecida como MSIL. É a linguagem de mais baixo nível na plataforma .NET. Análoga à linguagem Assembly nas plataformas x86 (não confundir com o conceito de assembly do .NET). As instruções em MSIL são bem mais cruas que em C#, por se tratar de uma linguagem mais próxima da máquina. Mesmo assim, é uma linguagem bem mais simples que Assembly puro, por ter conhecimento de estruturas de alto nível como métodos e objetos.