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)

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Mottor
  • 1,938
  • 3
  • 12
  • 29

3 Answers3

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();
    
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
svick
  • 236,525
  • 50
  • 385
  • 514
  • Can you see my other question? http://stackoverflow.com/questions/37482955/how-can-i-use-net-core-class-library-netstandard-v1-5-into-net-framework-4 – Mottor May 30 '16 at 20:44
  • 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. – Alex Zhukovskiy Jan 04 '17 at 22:35
  • @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. – svick Jan 04 '17 at 22:41
  • @svick I think I got answer on this question [here](https://github.com/dotnet/roslyn/issues/16211): `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 guess – Alex Zhukovskiy Jan 05 '17 at 11:42
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");
    }
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Gary Holland
  • 2,565
  • 1
  • 16
  • 17
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://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);
    }
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Konard
  • 2,298
  • 28
  • 21