Compilando e carregando DLLs em tempo de execução no .NET com o Roslyn
Soa até um pouco estranho a ideia de compilar uma DLL enquanto seu código está executando, a primeira vez que li sobre isso pensei “pra que eu iria precisar disso?”.
A verdade é que dependendo de como o seu serviço é oferecido, chega até a ser inviável que ele não compile código enquanto executa. Um exemplo bem simples e usual: existem diversos sites onde é possível escrever e testar códigos fonte de forma rápida — primeiro que seria impossível prever a infinidade de códigos que podem ser compilados, segundo que seria completamente inviável parar e rodar a execução do seu serviço a cada compilação. Claro que essa não é a única utilidade desse recurso, uma infinidade de possibilidades está aberta.
O responsável por toda essa “mágica” no .NET é o Roslyn,o mesmo disponibiliza uma API de código aberto para tal.
Pré-requisitos
- .NET Core instalado (para esse exemplo, porém, também é possível utilizar no .NET padrão)
Pacotes necessários
- Microsoft.CodeAnalysis.CSharp (para compilar e analisar códigos)
- System.Runtime.Loader (para carregar o assembly)
Exemplo passo a passo
Existem basicamente duas formas de montar o código que será compilado, a forma que eu acho mais “natural” realizando um parser de um código fonte ou utilizando os método da API para montar toda a árvore de sintaxe. Neste post demonstro como gerar a mesma DLL utilizando as duas formas.
O código fonte da nossa DLL de demonstração será:

Exemplificando as duas formas, podemos montar a árvore via parser, criando a string:

E em seguida transformar no objeto SyntaxTree:

Ou montar totalmente via API:

Os códigos fontes mostrados acima, quando compilados pelo Roslyn, serão idênticos. Claro que nesse exemplo podemos notar que montar o código via API é bem mais complexo do que utilizar o parser, porém, seu novo código pode ser tão dinâmico ao ponto que não é possível prever a estrutura. Tentando simplificar, seria como utilizar o parser fosse viável quando temos um template do código a ser gerado, já via API o código será completamente dinâmico.
Muito bem, considerando que ambos os códigos acima nos entregaram o objeto SyntaxTree, podemos montar a compilação:

No método estamos criando a estrutura de compilação no Roslyn,onde:
- assemblyName é o nome do nosso novo assembly
- syntaxTrees é a lista de sintaxes que vão ser compiladas
- references é a lista de referências de outras DLLs utilizadas no código que será compilado
Para nosso exemplo, precisamos da DLL System.Runtime, que pode ser obtida por:

Finalmente podemos criar a nova DLL:

Ao chamar o método compilation.Emit(file) será realizada a compilação e podemos obter os diagnósticos que também podem ser utilizado para tratamento de erros.
É possível carregar essa nova DLL em sua aplicação com:

A partir daí o uso da DLL pode ser feito com ajuda do Reflection.
Com esse exemplo simples, observamos que o Roslyn nos abre um grande leque de possibilidades quando pensamos em alterar o comportamento ao longo da execução.
Espero que eu tenha conseguido ser claro e que você tenha gostado.
Muito obrigado pela leitura!
Um código fonte de exemplo está disponível em meu GitHub.
