Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Compiling and running code at runtime in .NET Core 1.0

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)

like image 534
Mottor Avatar asked May 30 '16 12:05

Mottor


People also ask

How .NET core code is compiled?

Compiling MSIL to Native CodeAt execution time, a just-in-time (JIT) compiler translates the MSIL into native code. During this compilation, code must pass a verification process that examines the MSIL and metadata to find out whether the code can be determined to be type safe.

What is the runtime of .NET core?

NET releases in odd-numbered years are Long-term Support (LTS) releases and are supported for three years. Releases in even-numbered years are Short-term Support (STS) releases and are supported for 18 months.

Does C# compile at runtime?

C# is typically interpreted into bytecode which is compiled by the CLR, the common language runtime, another virtual machine.

What is Readytorun compilation?

R2R is a form of ahead-of-time (AOT) compilation. R2R binaries improve startup performance by reducing the amount of work the just-in-time (JIT) compiler needs to do as your application loads. The binaries contain similar native code compared to what the JIT would produce.


3 Answers

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();
    
like image 183
svick Avatar answered Oct 06 '22 11:10

svick


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");
    }
}
like image 34
Gary Holland Avatar answered Oct 06 '22 10:10

Gary Holland


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://stackoverflow.com/a/39260735/710069 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);
    }
}
like image 26
Konard Avatar answered Oct 06 '22 10:10

Konard