How to migrate from System.CodeDom.Compiler to Roslyn API?

440 Views Asked by At

I have the following piece of code in .NET Framework 4.8,

 sourceCode = $@"  
using System;
{string.Format(Constants.Assembly.UsingDirective, Constants.Assembly.DapperNamespace)}

namespace {Constants.Assembly.DynamicTypeNamespace} {{  
    {sourceCode}
}}";

  // Create Compilation Parameters
                    CompilerParameters compileParams = new CompilerParameters()
                    {
                        CompilerOptions = Constants.Assembly.CompileToLibrary,
                        GenerateInMemory = true
                    };
                    compileParams.ReferencedAssemblies.AddRange(baseAssemblyLocations.ToArray());

                    // Create Code Provider
                    CSharpCodeProvider provider = new CSharpCodeProvider(new Dictionary<string, string>() {
                            { Constants.Assembly.CompilerVersion, Constants.Assembly.CompilerVersion4 }
                        });

                    // Attempt compilation
                    CompilerResults compileResult = provider.CompileAssemblyFromSource(compileParams, sourceCode);
                    if (compileResult.Errors.Count > 0)
                    {
                        throw new Exception(compileResult.Errors[0].ErrorText);
                    }

                    // Store the assembly
                    Assembly = compileResult.CompiledAssembly;

I am looking into Roslyn APIs, but can't get it working using CSharpCompilationOptions.

How should I pass compilerParams, and sourceCode to the CSharpCompilationOptions?

1

There are 1 best solutions below

9
Guru Stron On

How should I pass compilerParams

You don't, CompilerParameters is part of System.CodeDom.Compiler API, not a Roslyn API

To create a compilation you can use CSharpCompilation.Create:

var options = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary);

var compilation = CSharpCompilation.Create(
   "compilation",
   new[] {ParseText(sourceCode)},
   GetGlobalReferences(),
   options
);

Where ParseText is just:

SyntaxTree ParseText(string s)
{
    return CSharpSyntaxTree.ParseText(s, new CSharpParseOptions(LanguageVersion.Latest));
}

And GetGlobalReferences is something like (rename and modify it to find all needed references):

private static PortableExecutableReference[] GetGlobalReferences()
{
    var assemblies = new[]
    {
        typeof(object).Assembly,
        typeof(Console).Assembly
    };
    var returnList = assemblies
        .Select(a => MetadataReference.CreateFromFile(a.Location))
        .ToList();
    //The location of the .NET assemblies
    var assemblyPath = Path.GetDirectoryName(typeof(object).Assembly.Location)!;
    /*
        * Adding some necessary .NET assemblies
        * These assemblies couldn't be loaded correctly via the same construction as above,
        * in specific the System.Runtime.
        */
    returnList.Add(MetadataReference.CreateFromFile(Path.Combine(assemblyPath, "mscorlib.dll")));
    returnList.Add(MetadataReference.CreateFromFile(Path.Combine(assemblyPath, "System.dll")));
    returnList.Add(MetadataReference.CreateFromFile(Path.Combine(assemblyPath, "System.Core.dll")));
    returnList.Add(MetadataReference.CreateFromFile(Path.Combine(assemblyPath, "System.Runtime.dll")));
    return returnList.ToArray();
}

And then you should be able to emit assembly:

compilation.Emit("path_to_save");

I use similar code to unit test my pet project source generator.