Compiling and Running code at runtime in .Net Core 1.0

后端 未结 3 547
再見小時候
再見小時候 2020-12-03 03:39

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 pa

相关标签:
3条回答
  • 2020-12-03 04:01

    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 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();
      
    0 讨论(0)
  • 2020-12-03 04:03

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

    So with the help of 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);
        }
    }
    
    0 讨论(0)
  • 2020-12-03 04:12

    Just adding to @svick option one 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 that in Net451 where the code is:

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

    My code targets both Net451 and Netstandard 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");
                            }
                        }
    
    0 讨论(0)
提交回复
热议问题