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.
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.
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.
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!