Hasta ahora, hemos analizado principalmente cómo podemos usar Roslyn para analizar y manipular el código fuente. Ahora veremos cómo finalizar el proceso de compilación emitiéndolo en el disco o en la memoria. Para comenzar, solo intentaremos emitir una compilación simple al disco y verificar si tuvo éxito o no.
var tree = CSharpSyntaxTree.ParseText(@"
using System;
public class C
{
public static void Main()
{
Console.WriteLine(""Hello World!"");
Console.ReadLine();
}
}");
var mscorlib = MetadataReference.CreateFromFile(typeof(object).Assembly.Location);
var compilation = CSharpCompilation.Create("MyCompilation",
syntaxTrees: new[] { tree }, references: new[] { mscorlib });
//Emitting to file is available through an extension method in the Microsoft.CodeAnalysis namespace
var emitResult = compilation.Emit("output.exe", "output.pdb");
//If our compilation failed, we can discover exactly why.
if(!emitResult.Success)
{
foreach(var diagnostic in emitResult.Diagnostics)
{
Console.WriteLine(diagnostic.ToString());
}
}
Después de ejecutar este código, podemos ver que nuestro ejecutable y .pdb se han emitido a Debug/bin/. Podemos hacer doble clic en output.exe y ver que nuestro programa se ejecuta como se esperaba. El archivo .pdb es opcional. Escribir el archivo .pdb en el disco puede llevar bastante tiempo y, a menudo, vale la pena omitir este argumento a menos que realmente se necesite.
A veces es posible que no queramos emitir a disco. Es posible que solo queramos compilar el código, enviarlo a la memoria y luego ejecutarlo desde la memoria. Para esto, la API de script probablemente tenga más sentido de usar. Aún así, vale la pena conocer nuestras opciones.
var tree = CSharpSyntaxTree.ParseText(@"
using System;
public class MyClass
{
public static void Main()
{
Console.WriteLine(""Hello World!"");
Console.ReadLine();
}
}");
var mscorlib = MetadataReference.CreateFromFile(typeof(object).Assembly.Location);
var compilation = CSharpCompilation.Create("MyCompilation",
syntaxTrees: new[] { tree }, references: new[] { mscorlib });
//Emit to stream
var ms = new MemoryStream();
var emitResult = compilation.Emit(ms);
//Load into currently running assembly. Normally we'd probably
//want to do this in an AppDomain
var ourAssembly = Assembly.Load(ms.ToArray());
var type = ourAssembly.GetType("MyClass");
//Invokes our main method and writes "Hello World" 🙂
type.InvokeMember("Main", BindingFlags.Default | BindingFlags.InvokeMethod, null, null, null);
Finalmente, ¿qué pasa si queremos influir en cómo se compila nuestro código? Es posible que queramos permitir código no seguro, marcar advertencias como errores o retrasar la firma del ensamblado. Todas estas opciones se pueden personalizar pasando un objeto CSharpCompilationOptions a CSharpCompilation.Create(). Echaremos un vistazo a cómo podemos interactuar con algunas de estas propiedades a continuación.
var tree = CSharpSyntaxTree.ParseText(@"
using System;
public class MyClass
{
public static void Main()
{
Console.WriteLine(""Hello World!"");
Console.ReadLine();
}
}");
//We first have to choose what kind of output we're creating: DLL, .exe etc.
var options = new CSharpCompilationOptions(OutputKind.ConsoleApplication);
options = options.WithAllowUnsafe(true); //Allow unsafe code;
options = options.WithOptimizationLevel(OptimizationLevel.Release); //Set optimization level
options = options.WithPlatform(Platform.X64); //Set platform
var mscorlib = MetadataReference.CreateFromFile(typeof(object).Assembly.Location);
var compilation = CSharpCompilation.Create("MyCompilation",
syntaxTrees: new[] { tree },
references: new[] { mscorlib },
options: options);
En total hay unas veinticinco opciones diferentes disponibles para la personalización. Básicamente, cualquier opción que tiene la página de propiedades del proyecto de Visual Studio debería estar disponible aquí.
Hay algunos parámetros opcionales disponibles en Compilation.Emit() que vale la pena analizar.
xmlDocPath: genera automáticamente documentación XML basada en los comentarios de documentación presentes en sus clases, métodos, propiedades, etc.
manifestResources: le permite incrustar manualmente recursos como cadenas e imágenes dentro del ensamblaje emitido.
win32ResourcesPath: ruta del archivo desde el que se leerán los recursos Win32 de la compilación (en formato RES).