Revista MSDN Magazine Edição 25 - Controles em runtime com Reflection e CodeDOM

Artigo Originalmente Publicado na MSDN Magazine Edição 25

Clique aqui para ler todos os artigos desta edição

 

Controles em runtime com Reflection e CodeDOM

 

 

Uma das características freqüentemente negligenciadas do Microsoft .NET Framework é a habilidade para gerar, compilar e executar código personalizado em tempo de execução (runtime). Isso é feito, por exemplo, durante a serialização dos dados XML e no uso de expressões regulares, onde a função de avaliação de expressões é gerada em tempo de execução.

Este artigo descreve outra área na qual a geração de código em tempo de execução pode ser usada: na criação de controles de IU (interface de usuário). Gerar esses controles uma única vez e então reutilizá-los quando necessário é muito mais eficiente que gerá-los cada vez que um formulário ou página são requisitados.

Isso se aplica a qualquer aplicação que tem campos configuráveis pelo usuário (por exemplo, quando o usuário final pode selecionar itens de dados a serem exibidos na tela). Geralmente definem-se formulários personalizados usando XML. Esses são analisados gramaticalmente em tempo de execução para a construção dinâmica da IU quando a página for carregada. Porém, essa análise gramatical freqüentemente acontece cada vez que o formulário é exibido, ou, em um cenário de servidor, para cada usuário, criando uma sobrecarga desnecessária para a aplicação.

Neste artigo, forneceremos exemplos detalhados de como usar a geração de código de tempo de execução para construir, carregar e executar controles em runtime. Os exemplos que descreveremos podem ser igualmente aplicados ao .NET Framework 1.x e 2.0. Existem alguns acréscimos significativos no namespace  Reflection na versão 2.0 do framework, mas essas mudanças não invalidam nem prejudicam de forma alguma, as soluções aqui apresentadas.

Fundamentos da geração de código

Muitas aplicações disponibilizam uma interface de usuário, a qual pode ser personalizada - possivelmente adicionando campos extras ou alterando a ordem e posição dos campos existentes. Isso é freqüentemente realizado em um dos seguintes dois modos:

·O usuário faz mudanças editando a interface do usuário dentro do Visual Studio;

·A aplicação gera controles em runtime baseados em algum formulário de dados de configuração, geralmente armazenado em um arquivo XML;

Nenhuma dessas soluções é ideal. A solução realmente necessária é uma combinação desses dois métodos tal que ofereça o desempenho da solução manual e a flexibilidade da aplicação que gera os controles em runtime.

Há dois métodos suportados para construir controles em runtime: Reflection.Emit e System.CodeDom. O primeiro, que requer íntimo conhecimento da Linguagem Intermediária (IL), gera assemblies gerenciados usando instruções IL explicitamente especificadas. O segundo, usa um modelo orientado a objeto para gerar código-fonte que pode ser compilado então em IL e executado. Ambos os métodos serão abordados neste artigo.

De fato existe um outro método, uma variante da abordagem CodeDOM, que consiste em construir código-fonte manualmente, significando escrever nós mesmos os arquivos .cs ou .vb, em lugar de usar o modelo de objeto para fazê-lo, e então fazer a compilação em tempo de execução. Apesar disso ser possível, recomendamos usar um dos mecanismos previamente mencionados em lugar de uma solução feita à mão.

Classes geradoras que usam Reflection.Emit

O namespace Reflection.Emit permite gerar assemblies que são completamente temporários; podem ser gerados em memória e executados sem nunca serem persistidos em disco. No entanto, caso necessário, a opção para escrever em disco está disponível (discutiremos isto mais adiante no artigo).

Para gerar uma classe que use o Reflection.Emit, são exigidos os seguintes passos:

·Definir um assembly dinâmico;

·Criar um módulo dentro do assembly;

·Definir um tipo no módulo derivando o tipo da classe básica e interface(s) apropriada(s): criar os métodos naquele tipo, obter um ILGenerator para cada método, gerar o IL apropriado e persistir o tipo;

·Opcionalmente, salvar o assembly para uso futuro.

A construção de classes que usam Reflection.Emit representa uma abordagem complexa, e para uma determinada operação de código-fonte (tal como chamar um método) teremos que gerar tipicamente várias linhas de código de IL. Mais adiante, retornaremos a esse assunto.

Gerando classes usando o System.CodeDom

O namespace System.CodeDom disponibiliza um modo independente de linguagem para definir tipos. Constrói um modelo em memória do código e então usa um gerador de código para gerar o código-fonte para aquele modelo. Contanto que tenha um gerador de código apropriado, pode gerar o mesmo em qualquer linguagem (o .NET Framework Redistributable inclui geradores de código para o Visual Basic, C# e JScript; caso o Visual Studio for instalado, geradores para C++ e J# também estarão disponíveis).

Para gerar uma classe usando o CodeDOM precisamos:

·Criar uma nova CodeCompileUnit;

·Acrescentar namespaces à CodeCompileUnit;

·Adicionar as declarações de importação apropriadas;

·Adicionar um CodeTypeDeclaration para a classe a ser construída;

·Adicionar um CodeExpressions para cada método;

·Obter um compilador de código e compilar o CodeCompileUnit.

Um dos benefícios desse método é o uso de conceitos de alto-nível, se comparado ao Reflection.Emit. Uma vez que as classes estejam definidas, precisamos construir instâncias em runtime. O modo mais comum é usar a classe Activator para carregar o tipo, como mostrado no seguinte trecho de código:

 

public Control LoadControl ( string typeName )

{

return LoadControl(Type.GetType(typeName));

}

 

public Control LoadControl ( Type controlType )

{

return Activator.CreateInstance(controlType) as Control;

}

 

Agora chamamos o membro GetType estático da classe Type. O GetType devolve a informação de tipo .NET para o tipo designado no argumento. A classe Activator é usada então para construir uma instância do tipo. Note que se o tipo especificado estiver em um assembly diferente de mscorlib ou do assembly atualmente em execução, o nome do tipo deve ser qualificado com o nome do assembly. Quando armazenados como uma string, os nomes de tipos gerenciados podem ser qualificados parcialmente com o namespace do tipo, ou totalmente com ambos o namespace do tipo e com o nome do assembly no qual está armazenado. Eis aqui uma instância de um nome de tipo qualificado no assembly:

 

TestControls.TestControl, TestAssembly

 

Esse nome de tipo corresponde a um tipo como mostrado no seguinte código, compilado em um " [...] continue lendo...

Ebook exclusivo
Dê um upgrade no início da sua jornada. Crie sua conta grátis e baixe o e-book

Artigos relacionados