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)
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.
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.
C# is typically interpreted into bytecode which is compiled by the CLR, the common language runtime, another virtual machine.
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.
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();
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");
    }
}
                        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);
    }
}
                        If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With