Compiling and running code at runtime in .NET Core 1.0
Asked Answered
B

3

20

Is it possible to compile and run C# code at runtime in the new .NET Core (better .NET Standard Platform)?

I have seen some examples (.NET Framework), but they used NuGet packages that are not compatible with netcoreapp1.0 (.NETCoreApp,Version=v1.0)

Banyan answered 30/5, 2016 at 12:52 Comment(0)
I
31

Option #1: Use the full C# compiler to compile an assembly, load it and then execute a method from it.

This requires the following packages as dependencies in your project.json:

"Microsoft.CodeAnalysis.CSharp": "1.3.0-beta1-20160429-01",
"System.Runtime.Loader": "4.0.0-rc2-24027",

Then you can use code like this:

var compilation = CSharpCompilation.Create("a")
    .WithOptions(new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary))
    .AddReferences(
        MetadataReference.CreateFromFile(typeof(object).GetTypeInfo().Assembly.Location))
    .AddSyntaxTrees(CSharpSyntaxTree.ParseText(
        @"
using System;

public static class C
{
    public static void M()
    {
        Console.WriteLine(""Hello Roslyn."");
    }
}"));

var fileName = "a.dll";

compilation.Emit(fileName);

var a = AssemblyLoadContext.Default.LoadFromAssemblyPath(Path.GetFullPath(fileName));

a.GetType("C").GetMethod("M").Invoke(null, null);

Option #2: Use Roslyn Scripting. This will result in much simpler code, but it currently requires more setup:

  • Create NuGet.config to get packages from the Roslyn nightly feed:

      <?xml version="1.0" encoding="utf-8"?>
      <configuration>
        <packageSources>
          <add key="Roslyn Nightly" value="https://www.myget.org/F/roslyn-nightly/api/v3/index.json" />
        </packageSources>
      </configuration>
    
  • Add the following package as a dependency to project.json (notice that this is package from today. You will need different version in the future):

      "Microsoft.CodeAnalysis.CSharp.Scripting": "1.3.0-beta1-20160530-01",
    

    You also need to import dotnet (obsolete "Target Framework Moniker", which is nevertheless still used by Roslyn):

      "frameworks": {
        "netcoreapp1.0": {
          "imports": "dotnet5.6"
        }
      }
    
  • Now you can finally use Scripting:

      CSharpScript.EvaluateAsync(@"using System;Console.WriteLine(""Hello Roslyn."");").Wait();
    
Imperturbation answered 30/5, 2016 at 17:28 Comment(4)
Can you see my other question? #37483455Banyan
Your example is working fine, however it's not possible to add ReadLine or WriteLine with parameters. Where can I get documentation about what overloads and methods exists? Because it's very weird when Console has Write methods but doesn't have any ReadLine.Lujan
@AlexZhukovskiy If you can use Console.WriteLine, Console.ReadLine or other overloads of Console.WriteLine should work too. It's hard for me to guess what could be wrong with your code, you might want to ask a new question with full details.Imperturbation
@Imperturbation I think I got answer on this question here: actual mscorlib which contains Object (lets call it System.Private.Corlib) isn't usable as a reference library. It has a number of inconsistencies and duplicate types that make it unsuitable as a reference assembly. Code which is normally compilable won't compile when using System.Private.Corlib due to these changes. Hence even if Roslyn found it, the code isn't guaranteed to compile. It really needs a referencable version of mscorlib in order to function here. So it's a known issue I guessLujan
G
12

I am just adding to svick's answer. If you want to keep the assembly in memory (rather than writing to a file) you can use the following method:

AssemblyLoadContext context = AssemblyLoadContext.Default;
Assembly assembly = context.LoadFromStream(ms);

This is different than in .NET 4.5.1 where the code is:

Assembly assembly = Assembly.Load(ms.ToArray());

My code targets both .NET 4.5.1 and .NET Standard, so I had to use directives to get around this problem. The full code example is here:

string code = CreateFunctionCode();
var syntaxTree = CSharpSyntaxTree.ParseText(code);

MetadataReference[] references = new MetadataReference[]
{
    MetadataReference.CreateFromFile(typeof(object).GetTypeInfo().Assembly.Location),
    MetadataReference.CreateFromFile(typeof(Hashtable).GetTypeInfo().Assembly.Location)
};

var compilation = CSharpCompilation.Create("Function.dll",
   syntaxTrees: new[] { syntaxTree },
   references: references,
   options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));

StringBuilder message = new StringBuilder();

using (var ms = new MemoryStream())
{
    EmitResult result = compilation.Emit(ms);

    if (!result.Success)
    {
        IEnumerable<Diagnostic> failures = result.Diagnostics.Where(diagnostic =>
            diagnostic.IsWarningAsError ||
            diagnostic.Severity == DiagnosticSeverity.Error);

        foreach (Diagnostic diagnostic in failures)
        {
            message.AppendFormat("{0}: {1}", diagnostic.Id, diagnostic.GetMessage());
        }

        return new ReturnValue<MethodInfo>(false, "The following compile errors were encountered: " + message.ToString(), null);
    }
    else
    {
        ms.Seek(0, SeekOrigin.Begin);

        #if NET451
            Assembly assembly = Assembly.Load(ms.ToArray());
        #else
            AssemblyLoadContext context = AssemblyLoadContext.Default;
            Assembly assembly = context.LoadFromStream(ms);
        #endif

        Type mappingFunction = assembly.GetType("Program");
        _functionMethod = mappingFunction.GetMethod("CustomFunction");
        _resetMethod = mappingFunction.GetMethod("Reset");
    }
}
Gobble answered 10/6, 2016 at 3:57 Comment(2)
AssemblyLoadContext does not exists in .Net CoreLujan
@AlexZhukovskiy AssemblyLoadContext exists only in .Net Core, in the System.Runtime.Loader package.Imperturbation
V
6

Both previous answers didn't work for me in a .NET Core 2.2 environment on Windows. More references are needed.

So with the help of the https://mcmap.net/q/662252/-net-core-amd-roslyn-csharpcompilation-the-type-39-object-39-is-defined-in-an-assembly-that-is-not-referenced solution, I have ended up with this code:

var dotnetCoreDirectory = System.Runtime.InteropServices.RuntimeEnvironment.GetRuntimeDirectory();

var compilation = CSharpCompilation.Create("LibraryName")
    .WithOptions(new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary))
    .AddReferences(
        MetadataReference.CreateFromFile(typeof(object).GetTypeInfo().Assembly.Location),
        MetadataReference.CreateFromFile(typeof(Console).GetTypeInfo().Assembly.Location),
        MetadataReference.CreateFromFile(Path.Combine(dotnetCoreDirectory, "mscorlib.dll")),
        MetadataReference.CreateFromFile(Path.Combine(dotnetCoreDirectory, "netstandard.dll")),
        MetadataReference.CreateFromFile(Path.Combine(dotnetCoreDirectory, "System.Runtime.dll")))
    .AddSyntaxTrees(CSharpSyntaxTree.ParseText(
        @"public static class ClassName
        {
            public static void MethodName() => System.Console.WriteLine(""Hello C# Compilation."");
        }"));

// Debug output. In case your environment is different it may show some messages.
foreach (var compilerMessage in compilation.GetDiagnostics())
    Console.WriteLine(compilerMessage);

Than output library to file:

var fileName = "LibraryName.dll";
var emitResult = compilation.Emit(fileName);
if (emitResult.Success)
{
    var assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(Path.GetFullPath(fileName));

    assembly.GetType("ClassName").GetMethod("MethodName").Invoke(null, null);
}

or to memory stream:

using (var memoryStream = new MemoryStream())
{
    var emitResult = compilation.Emit(memoryStream);
    if (emitResult.Success)
    {
        memoryStream.Seek(0, SeekOrigin.Begin);

        var context = AssemblyLoadContext.Default;
        var assembly = context.LoadFromStream(memoryStream);

        assembly.GetType("ClassName").GetMethod("MethodName").Invoke(null, null);
    }
}
Vasos answered 28/3, 2019 at 20:47 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.