0

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?

tRuEsAtM
  • 3,517
  • 6
  • 43
  • 83

1 Answers1

0

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.

Guru Stron
  • 102,774
  • 10
  • 95
  • 132
  • As per the last line, I need to assign the compilation result assembly to the Assembly object. But,`compilation.Emit` doesn't seem to be working in that way. – tRuEsAtM Jul 27 '22 at 00:20
  • @tRuEsAtM you can emit into a memory stream (there is such overload) and then create an assembly via `Assembly assembly = Assembly.Load(memoryStream.ToArray())` (do not forget to adjust position in the stream for example via `memoryStream.Seek(0, SeekOrigin.Begin);`) – Guru Stron Jul 27 '22 at 00:26
  • Like this? `var ms = new MemoryStream(); compilation.Emit(ms); Assembly = Assembly.Load(ms.ToArray());` – tRuEsAtM Jul 27 '22 at 00:32
  • @tRuEsAtM yes, looks almost right - do not forget to move the position in the stream. – Guru Stron Jul 27 '22 at 00:33
  • I don't understand where to apply Seek. – tRuEsAtM Jul 27 '22 at 00:36
  • 1
    `using var ms = new MemoryStream(); compilation.Emit(ms); ms.Seek(0, SeekOrigin.Begin);Assembly = Assembly.Load(ms.ToArray());` – Guru Stron Jul 27 '22 at 00:37
  • By chance do you know anything about https://stackoverflow.com/questions/73130090/migrating-from-assembly-reflectiononlyload-to-metadataloadcontext? This is where I am actually stuck. – tRuEsAtM Jul 27 '22 at 00:41
  • I got ```Bad IL format.``` exception. – tRuEsAtM Jul 27 '22 at 01:02
  • @tRuEsAtM check if emit result is success and `ms.ToArray()` is not an empty array. – Guru Stron Jul 27 '22 at 01:08