Introduction
When Roslyn fails to compile code, the problem is usually not "Roslyn is broken." More often, the compilation is missing metadata references, using the wrong language version, or feeding the compiler source that parses differently than expected. The fastest way to debug it is to inspect Roslyn diagnostics instead of treating Emit failure as a single opaque error.
Build a Minimal Compilation and Read Diagnostics
Roslyn compiles source in stages: parse the syntax tree, bind symbols against referenced assemblies, then emit an assembly. If any stage fails, diagnostics tell you where it happened.
Here is a minimal in-memory compilation:
1using System;
2using System.IO;
3using Microsoft.CodeAnalysis;
4using Microsoft.CodeAnalysis.CSharp;
5
6var source = @"
7using System;
8
9public static class Program
10{
11 public static void Main()
12 {
13 Console.WriteLine(""Hello from Roslyn"");
14 }
15}";
16
17var syntaxTree = CSharpSyntaxTree.ParseText(source);
18
19var references = new[]
20{
21 MetadataReference.CreateFromFile(typeof(object).Assembly.Location),
22 MetadataReference.CreateFromFile(typeof(Console).Assembly.Location),
23};
24
25var compilation = CSharpCompilation.Create(
26 assemblyName: "Demo",
27 syntaxTrees: new[] { syntaxTree },
28 references: references,
29 options: new CSharpCompilationOptions(OutputKind.ConsoleApplication)
30);
31
32using var stream = new MemoryStream();
33var result = compilation.Emit(stream);
34
35foreach (var diagnostic in result.Diagnostics)
36{
37 Console.WriteLine(diagnostic);
38}
39
40Console.WriteLine($"Success: {result.Success}");
If result.Success is false, the diagnostics collection is the real answer. It tells you whether the failure came from syntax, missing types, invalid assembly references, or code generation.
Missing References Are the Most Common Cause
If your dynamic compilation uses only the syntax tree and forgets framework references, code that looks perfectly valid will fail with messages about unresolved types or namespaces. This happens often when developers assume Roslyn automatically knows about System.Console, List<T>, or ASP.NET types.
At minimum, add the assemblies that contain the types your code uses. For simple samples that usually includes System.Private.CoreLib, System.Console, and sometimes System.Runtime. In real applications, build the reference list from the current runtime or from the project being analyzed.
1using System.Linq;
2using System.Reflection;
3using Microsoft.CodeAnalysis;
4
5var trustedAssemblies = ((string?)AppContext.GetData("TRUSTED_PLATFORM_ASSEMBLIES"))!
6 .Split(Path.PathSeparator);
7
8var references = trustedAssemblies
9 .Where(path => path.Contains("System.Private.CoreLib")
10|| path.Contains("System.Console") || path.Contains("System.Runtime")) .Select(path => MetadataReference.CreateFromFile(path)) .ToArray(); ``` That approach is more reliable than hardcoding a few DLL paths by hand, especially across local machines and build agents. ## Check Parse Options and Language Version Compilation can also fail because the source uses syntax newer than the parser settings. Features such as file-scoped namespaces, top-level statements, pattern matching improvements, or collection expressions depend on the configured C# language version. ```csharp var parseOptions = new CSharpParseOptions(LanguageVersion.CSharp12); var syntaxTree = CSharpSyntaxTree.ParseText(source, parseOptions); ``` If you omit parse options in a tool that targets an older default, the same source may compile in your project but fail in your Roslyn host. This mismatch is common in analyzers, scripting tools, code generators, and custom build steps. ## Emit Failures vs. Diagnostic Failures Some failures occur even when parsing and binding succeed. For example, Roslyn may compile the tree correctly but fail during emit because the output stream is not writable, the assembly name is invalid, or the generated code references unavailable runtime features. A good debugging pattern is to separate syntax diagnostics from emit diagnostics: ```csharp foreach (var diagnostic in syntaxTree.GetDiagnostics()) { Console.WriteLine($"Parse: {diagnostic}"); } foreach (var diagnostic in compilation.GetDiagnostics()) { Console.WriteLine($"Compile: {diagnostic}"); } ``` This tells you whether the issue started at parsing time or later during semantic analysis and emission. ## Common Pitfalls The biggest pitfall is treating `Emit` returning `false` as the whole error. Roslyn always provides diagnostics, and those messages are much more actionable than the boolean result. Another common mistake is loading the wrong framework references for the runtime you are actually targeting. Code compiled against one .NET runtime may fail when the host process resolves assemblies from another. Language version mismatches are also easy to miss. If the same source compiles in a regular project but not in a Roslyn tool, compare parse options and target framework assumptions before changing the source. ## Summary - Roslyn failures are usually caused by diagnostics you can inspect directly. - Start with `compilation.GetDiagnostics()` and `result.Diagnostics` instead of guessing. - Missing metadata references are one of the most common reasons valid-looking code does not compile. - Match `CSharpParseOptions` to the language features used by the source. - Separate parse, bind, and emit failures so you debug the right stage of compilation.