在.NET Core 1.0中运行时编译和执行代码

20

在新的.NET Core(更好的.NET标准平台)中,是否有可能在运行时编译和运行C#代码?

我看过一些示例(.NET Framework),但它们使用的NuGet包不兼容于netcoreapp1.0(.NETCoreApp,Version=v1.0)。

3个回答

31

选项#1:使用完整的C#编译器编译一个程序集,加载它,然后执行其中的一个方法。

这需要在你的project.json文件中添加以下依赖项:

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

然后您可以使用如下代码:
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);

方案二:使用Roslyn脚本。这将导致代码更简单,但目前需要更多设置:

  • 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();
    

你能看到我的另一个问题吗?http://stackoverflow.com/questions/37482955/how-can-i-use-net-core-class-library-netstandard-v1-5-into-net-framework-4 - Mottor
你的示例工作正常,但是无法添加带参数的ReadLineWriteLine。我在哪里可以查找有关现有重载和方法的文档?因为当控制台具有Write方法但不具有任何ReadLine方法时,这非常奇怪。 - Alex Zhukovskiy
@AlexZhukovskiy 如果你能使用 Console.WriteLine,那么其他重载的 Console.WriteLine 也应该可以工作。对于你的代码出了什么问题,我很难猜测,你可能需要提出一个新问题并提供完整的细节。 - svick
@svick 我想我在这个问题上找到了答案这里实际的mscorlib包含Object(我们称之为System.Private.Corlib)不能用作引用库。它有许多不一致和重复的类型,使其不适合作为参考程序集。使用System.Private.Corlib时,通常可编译的代码将无法编译。因此,即使Roslyn找到它,也不能保证代码能够编译。在这里,它真的需要一个可引用的mscorlib版本才能正常运行。所以我想这是一个已知的问题。 - Alex Zhukovskiy

12

我只是在添加到svick的答案。如果你想将程序集保存在内存中而不是写入文件,可以使用以下方法:

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

这与.NET 4.5.1中的代码不同,那里的代码如下:

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

我的代码同时针对 .NET 4.5.1 和 .NET Standard,所以我不得不使用指令来解决这个问题。完整的代码示例在这里:

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");
    }
}

AssemblyLoadContext does not exists in .Net Core - Alex Zhukovskiy
4
@AlexZhukovskiy AssemblyLoadContext 只存在于 .Net Core 中,位于 System.Runtime.Loader 包中。 - svick

6

之前的回答在我的Windows .NET Core 2.2环境下无法工作,需要更多的参考。

因此,在https://dev59.com/qFkS5IYBdhLWcg3w8ai-#39260735 的解决方案的帮助下,我最终得到了这段代码:

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);

将输出库写入文件:
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);
}

或者到内存流中:

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);
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接