web-dev-qa-db-ja.com

.Net Core1.0での実行時のコードのコンパイルと実行

新しい.NetCore(より優れた.Net Standard Platform)で実行時にC#コードをコンパイルして実行することは可能ですか?いくつかの例(.Net Framework)を見てきましたが、netcoreapp1.0(.NETCoreApp、Version = v1.0)と互換性のないNuGetパッケージを使用していました。

15
Mottor

オプション#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);

オプション#2:Roslynスクリプトを使用します。これにより、コードがはるかに単純になりますが、現在、より多くのセットアップが必要です。

  • NuGet.configを作成して、Roslynの夜間フィードからパッケージを取得します。

    <?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>
    
  • 次のパッケージを依存関係としてproject.jsonに追加します(これは今日のパッケージであることに注意してください。将来は別のバージョンが必要になります)。

    "Microsoft.CodeAnalysis.CSharp.Scripting": "1.3.0-beta1-20160530-01",
    

    また、dotnet(廃止された "Target Framework Moniker"、 それでもRoslynによって使用されています )をインポートする必要があります。

    "frameworks": {
      "netcoreapp1.0": {
        "imports": "dotnet5.6"
      }
    }
    
  • これで、ついにスクリプトを使用できるようになりました。

    CSharpScript.EvaluateAsync(@"using System;Console.WriteLine(""Hello Roslyn."");").Wait();
    
22
svick

@svickオプションに1つの答えを追加するだけです。 (ファイルに書き込むのではなく)アセンブリをメモリに保持する場合は、次の方法を使用できます。

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

これは、コードが次のNet451の場合とは異なります。

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

私のコードはNet451とNetstandardの両方を対象としているため、この問題を回避するにはディレクティブを使用する必要がありました。完全なコード例は次のとおりです。

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

以前の両方の回答は、Windows上の.NET Core2.2環境では機能しませんでした。より多くの参照が必要です。

だから https://stackoverflow.com/a/39260735/710069 ソリューションの助けを借りて、私はこのコードで終わった:

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