Up until now, we’ve mostly looked at how we can use Roslyn to analyze and manipulate source code. Now we’ll take a look at finishing the compilation process by emitting it disk or to memory. To start, we’ll just try emitting a simple compilation to disk and checking whether or not it succeeded.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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()); | |
} | |
} |
After running this code we can see that our executable and .pdb
have been emitted to Debug/bin/
. We can double click output.exe
and see that our program runs as expected. Keep in mind that the .pdb
file is optional. I’ve only chosen to emit it here to show off the API. Writing the .pdb
file to disk can take a fairly long time and it often pays to omit this argument unless you really need it.
Sometimes we might not want to emit to disk. We might just want to compile the code, emit it to memory and then execute it from memory. Keep in mind that for most cases where we’d want to do this, the scripting API probably makes more sense to use. Still, it pays to know our options.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); |
Finally, what if we want to influence how our code is compiled? We might want to allow unsafe code, mark warnings as errors or delay sign the assembly. All of these options can be customized by passing a CSharpCompilationOptions
object to CSharpCompilation.Create()
. We’ll take a look at how we can interact with a few of these properties below.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); //Pass options to compilation |
In total there are about twenty-five different options available for customization. Basically any option you have within the Visual Studio’s project property page should be available here.
Advanced options
There are a few optional parameters available in Compilation.Emit()
that are worth discussing. Some of them I’m familiar with, but others I’ve never used.
xmlDocPath
– Auto generates XML documentation based on the documentation comments present on your classes, methods, properties etc.manifestResources
– Allows you to manually embed resources such as strings and images within the emitted assembly. Batteries are not included with this API and it requires some heavy lifting if you want to embed.resx
resources within your assembly. We’ll explore this overload in a future blog post.win32ResourcesPath
– Path of the file from which the compilation’s Win32 resources will be read (in RES format). Unfortunately I haven’t used this API yet and I’m not at all familiar with Win32 Resources.- There is also the option to
EmitDifference
between two compilations. I’m not familiar with this API, and I’m not familiar with how you can apply these deltas to existing assemblies on disk or in memory. I hope to learn more about this API in the coming months.
That just about wraps up the Emit API. If you have any questions, feel free to ask them in the comments below.