Brincando com Rozlyn - Delegado Anônimo
Piadinha

Brincando com Rozlyn

Não, péra... (de novo!)

Postado por Marcelo Palladino em 27-12-2014

Desde de 2002 (ou um pouco antes para quem é velho e usou as versões beta), a cada nova versão do Visual Studio somos apresentados a recursos que melhoram a plataforma enormemente. Em 2005 foram os tipos genéricos, em 2007 tivemos o linq, 2010 apresentou o dynamic e 2012 facilitou nossas vidas com uma forma diferente (mais fácil e direta) de criar software assíncrono (async).

Agora em 2014 estamos sendo bombardeamos novamente com uma porrada de novidades que virão com o Visual Studio 2015. A plataforma está ficando cada vez mais madura, aberta e multiplataforma. Temos coisas novas no C#, no ASP.NET e no desenvolvimento de aplicativos e etc.

A cereja no bolo para mim é a nova plataforma de compilação do .NET, que fornece compiladores C# e Visual Basic que podem ser utilizados como serviço pelos desenvolvedores, o .NET compiler platform (Roslyn).

Mão na massa

Bóra então fazer alguma coisa. Para sua informação, estou usando o Visual Studio 2015 Preview (14.0.22310.1 DP) enquanto escrevo este artigo. Para começar, vou criar uma Console Application.

Criando uma aplicação console

Uma vez que aplicação está criada, vou adicionar o Roslyn ao projeto utilizando o Nuget (lembre-se que como o Roslyn é um Prerelease, isso deve ser considerado isso na pesquisa (-Prerelease option)).

Install-Package -Prerelease Microsoft.CodeAnalysis.CSharp

Para começar a brincadeira nesta época de mega sena da virada, digamos que o código que deve ser compilado seja parecido com a classe abaixo.

1 public class MyInnocentLuckyNumbersGenerator
2 {
3     public System.Collections.Generic.IEnumerable<int> GetLucky()
4     {
5         var r = new System.Random((int)System.DateTime.Now.Ticks);
6         for (int i = 1; i <= 6; i++)
7             yield return r.Next(1, 60);
8     }
9 }

Certo. Agora na classe Program da aplicação que estou fazendo as brincadeiras, vou criar um um método (CodeToCompile) cuja responsabilidade é retornar a string com o código que deve ser compilado.

 1 private static string CodeToCompile()
 2 {
 3     return @"
 4         public class MyInnocentLuckyNumbersGenerator
 5         {
 6             public System.Collections.Generic.IEnumerable<int> GetLucky()
 7             {
 8                 var r = new System.Random((int)System.DateTime.Now.Ticks);
 9                 for (int i = 1; i <= 6; i++)
10                     yield return r.Next(1, 60);
11             }
12         }";
13 }

Feito isso, vou colocar o Roslyn para trabalhar com base nesta string que contém o código de uma classe.

 1 static void Main(string[] args)
 2 {
 3     var syntaxTree = Microsoft.CodeAnalysis.CSharp.SyntaxFactory.ParseSyntaxTree(CodeForCompile());
 4 
 5     var compilation = Microsoft.CodeAnalysis.CSharp.CSharpCompilation
 6         .Create("PlayWithRoslyn.OnTheFlyAssembly")
 7         .AddSyntaxTrees(syntaxTree);
 8 
 9     foreach (var compilationDiagnostic in compilation.GetDiagnostics())
10         System.Console.WriteLine(compilationDiagnostic);
11 }

No método main, a primeira linha gera a árvore de syntax com base no código que deve ser compilado. Depois cria o que é chamado de compilação (passando o nome do assembly como parâmetro), adiciona a árvore de syntax e, por fim, o código faz um loop nos diagnósticos gerados pela compilação escrevendo na saída padrão cada um deles.

A saída gerada pela execução do programa é parecida com a figura abaixo.

Diagnósticos da compilação com Roslyn.

O que aconteceu? Pela análise dos diagnósticos pode-se perceber que falta uma referência. Neste caso especificamente falta referenciar a mscorlib. Vou adicionar a referência e analisar a saída novamente.

 1 static void Main(string[] args)
 2 {
 3     var syntaxTree = Microsoft.CodeAnalysis.CSharp.SyntaxFactory.ParseSyntaxTree(CodeForCompile());
 4 
 5     var mscorlibReference = Microsoft.CodeAnalysis.MetadataReference
 6         .CreateFromAssembly(typeof(string).Assembly);
 7 
 8     var compilation = Microsoft.CodeAnalysis.CSharp.CSharpCompilation
 9         .Create("PlayWithRoslyn.OnTheFlyAssembly")
10         .AddReferences(mscorlibReference)
11         .AddSyntaxTrees(syntaxTree);
12 
13     foreach (var compilationDiagnostic in compilation.GetDiagnostics())
14         System.Console.WriteLine(compilationDiagnostic);
15 }

O código é bem direto. Ele pega a referência com base em um assembly (que foi encontrado com base em um tipo) e adiciona na compilação (AddReferences).

A nova saída ainda apresenta um problema, mas ficou bem melhor.

Diagnósticos da compilação com Roslyn (segunda tentativa).

O problema agora se refere ao tipo do projeto gerado pela compilação. A compilação está esperando um ponto de entrada, o que para este exemplo não encaixa muito bem. Sendo assim, vou fazer uma terceira modificação que fará com que a compilação gere uma class library.

 1 static void Main(string[] args)
 2 {
 3     var syntaxTree = Microsoft.CodeAnalysis.CSharp.SyntaxFactory.ParseSyntaxTree(CodeToCompile());
 4     var mscorlibReference = Microsoft.CodeAnalysis.MetadataReference
 5         .CreateFromAssembly(typeof(string).Assembly);
 6 
 7     var compilationOptions = new Microsoft.CodeAnalysis.CSharp
 8         .CSharpCompilationOptions(Microsoft.CodeAnalysis.OutputKind.DynamicallyLinkedLibrary);
 9 
10     var compilation = Microsoft.CodeAnalysis.CSharp.CSharpCompilation
11         .Create("PlayWithRoslyn.OnTheFlyAssembly")
12         .WithOptions(compilationOptions)
13         .AddReferences(mscorlibReference)
14         .AddSyntaxTrees(syntaxTree);
15 
16     foreach (var compilationDiagnostic in compilation.GetDiagnostics())
17         System.Console.WriteLine(compilationDiagnostic);
18 }

O construtor da classe CSharpCompilationOptions recebe uma pancada (e bota pancada nisso!) de parâmetros, mas o Único obrigatório é o outputKind, que define o tipo de saída da compilação. Sendo assim, a terceira versão do código cria uma instância para definir as opções e a passa para a compilação. Com isso, a lista de diagnósticos estará vazia na próxima execução do programa.

Bom, era isso. No próximo artigo desta série vou continuar exatamente deste ponto e estender um pouco este exemplo.

Até mais!

Código fonte utilizado no artigo