C#コードフラグメントをテキストファイル(または任意の入力ストリーム)に保存し、それらを動的に実行することが可能かどうか疑問に思っていましたか?提供されたものがMain()ブロック内で問題なくコンパイルされると仮定すると、このコードをコンパイルおよび/または実行することは可能ですか?パフォーマンス上の理由から、コンパイルしたいと思います。
少なくとも、実装するために必要なインターフェイスを定義し、そのインターフェイスを実装するコード「セクション」を提供することができます。
C#コードフラグメントをテキストファイル(または任意の入力ストリーム)に保存し、それらを動的に実行することが可能かどうか疑問に思っていましたか?提供されたものがMain()ブロック内で問題なくコンパイルされると仮定すると、このコードをコンパイルおよび/または実行することは可能ですか?パフォーマンス上の理由から、コンパイルしたいと思います。
少なくとも、実装するために必要なインターフェイスを定義し、そのインターフェイスを実装するコード「セクション」を提供することができます。
回答:
C#/すべての静的.NET言語での最良の解決策は、そのようなことにはCodeDOMを使用することです。(注記として、その他の主な目的は、コードのビット、またはクラス全体を動的に構築することです。)
LukeHのブログからの短い短い例を以下に示します。これは、楽しみのためにLINQも使用しています。
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.CSharp;
using System.CodeDom.Compiler;
class Program
{
static void Main(string[] args)
{
var csc = new CSharpCodeProvider(new Dictionary<string, string>() { { "CompilerVersion", "v3.5" } });
var parameters = new CompilerParameters(new[] { "mscorlib.dll", "System.Core.dll" }, "foo.exe", true);
parameters.GenerateExecutable = true;
CompilerResults results = csc.CompileAssemblyFromSource(parameters,
@"using System.Linq;
class Program {
public static void Main(string[] args) {
var q = from i in Enumerable.Range(1,100)
where i % 2 == 0
select i;
}
}");
results.Errors.Cast<CompilerError>().ToList().ForEach(error => Console.WriteLine(error.ErrorText));
}
}
ここで最も重要なクラスCSharpCodeProvider
は、コンパイラーを使用してオンザフライでコードをコンパイルするクラスです。次にコードを実行する場合は、リフレクションを少し使用して、アセンブリを動的にロードして実行するだけです。
以下は、C#の別の例です(少し簡潔ではありますが)System.Reflection
。名前空間を使用してランタイムコンパイルされたコードを実行する方法を正確に示しています。
C#のコードをメモリにコンパイルし、 Roslynでアセンブリバイトを生成できます。これについてはすでに触れましたが、ここにRoslynの例をいくつか追加する価値があります。以下は完全な例です。
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Emit;
namespace RoslynCompileSample
{
class Program
{
static void Main(string[] args)
{
// define source code, then parse it (to the type used for compilation)
SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(@"
using System;
namespace RoslynCompileSample
{
public class Writer
{
public void Write(string message)
{
Console.WriteLine(message);
}
}
}");
// define other necessary objects for compilation
string assemblyName = Path.GetRandomFileName();
MetadataReference[] references = new MetadataReference[]
{
MetadataReference.CreateFromFile(typeof(object).Assembly.Location),
MetadataReference.CreateFromFile(typeof(Enumerable).Assembly.Location)
};
// analyse and generate IL code from syntax tree
CSharpCompilation compilation = CSharpCompilation.Create(
assemblyName,
syntaxTrees: new[] { syntaxTree },
references: references,
options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
using (var ms = new MemoryStream())
{
// write IL code into memory
EmitResult result = compilation.Emit(ms);
if (!result.Success)
{
// handle exceptions
IEnumerable<Diagnostic> failures = result.Diagnostics.Where(diagnostic =>
diagnostic.IsWarningAsError ||
diagnostic.Severity == DiagnosticSeverity.Error);
foreach (Diagnostic diagnostic in failures)
{
Console.Error.WriteLine("{0}: {1}", diagnostic.Id, diagnostic.GetMessage());
}
}
else
{
// load this 'virtual' DLL so that we can use
ms.Seek(0, SeekOrigin.Begin);
Assembly assembly = Assembly.Load(ms.ToArray());
// create instance of the desired class and call the desired function
Type type = assembly.GetType("RoslynCompileSample.Writer");
object obj = Activator.CreateInstance(type);
type.InvokeMember("Write",
BindingFlags.Default | BindingFlags.InvokeMethod,
null,
obj,
new object[] { "Hello World" });
}
}
Console.ReadLine();
}
}
}
他の人はすでに実行時にコードを生成する方法について良い答えを出しているので、私はあなたの2番目の段落に取り組むと思いました。私はこれについていくつかの経験を持っていますが、その経験から学んだ教訓を共有したいと思います。
少なくとも、実装するために必要なインターフェイスを定義し、そのインターフェイスを実装するコード「セクション」を提供することができます。
をinterface
基本型として使用すると、問題が発生する可能性があります。interface
将来、単一の新しいメソッドをに追加すると、interface
現在実装されているすべての既存のクライアント提供クラスが抽象化されます。つまり、実行時にクライアント提供クラスをコンパイルまたはインスタンス化できなくなります。
古いインターフェイスを出荷してから約1年後、サポートする必要のある大量の「レガシー」データを配布した後、新しいメソッドを追加するときにこの問題が発生しました。古いインターフェイスを継承した新しいインターフェイスを作成してしまいましたが、この方法では、利用可能なインターフェイスを確認する必要があったため、クライアントが提供したクラスの読み込みとインスタンス化が難しくなりました。
当時私が考えていた解決策の1つは、実際のクラスを以下のような基本型として使用することでした。クラス自体を抽象としてマークすることもできますが、すべてのメソッドは(抽象メソッドではなく)空の仮想メソッドでなければなりません。その後、クライアントは必要なメソッドをオーバーライドでき、既存のクライアント提供のコードを無効にすることなく、基本クラスに新しいメソッドを追加できます。
public abstract class BaseClass
{
public virtual void Foo1() { }
public virtual bool Foo2() { return false; }
...
}
この問題が当てはまるかどうかに関係なく、コードベースとクライアント提供のコードの間のインターフェイスのバージョン管理方法を検討する必要があります。
これが有用であることがわかりました-コンパイルしているC#が、これを発行しているコードでいくつかのクラスなどを使用するようにしたいので、コンパイルしたアセンブリが現在参照しているすべてを参照するようにします。
var refs = AppDomain.CurrentDomain.GetAssemblies();
var refFiles = refs.Where(a => !a.IsDynamic).Select(a => a.Location).ToArray();
var cSharp = (new Microsoft.CSharp.CSharpCodeProvider()).CreateCompiler();
var compileParams = new System.CodeDom.Compiler.CompilerParameters(refFiles);
compileParams.GenerateInMemory = true;
compileParams.GenerateExecutable = false;
var compilerResult = cSharp.CompileAssemblyFromSource(compileParams, code);
var asm = compilerResult.CompiledAssembly;
私の場合、名前が文字列に格納されているクラスを発行していました。このクラスには、という名前className
の単一のpublic staticメソッドがありGet()
、typeで返されましたStoryDataIds
。そのメソッドの呼び出しは次のようになります。
var tempType = asm.GetType(className);
var ids = (StoryDataIds)tempType.GetMethod("Get").Invoke(null, null);
警告:コンパイルは驚くほど、非常に遅い場合があります。比較的高速なサーバーでは、比較的単純な10行のコードチャンクが通常の優先順位で2〜10秒でコンパイルされます。CompileAssemblyFromSource()
Webリクエストのように、通常のパフォーマンスが期待されるものへの呼び出しを結び付けてはなりません。代わりに、優先度の低いスレッドで必要なコードをプロアクティブにコンパイルし、コンパイルが完了するまで、そのコードを準備しておく必要があるコードを処理する方法があります。たとえば、バッチジョブプロセスで使用できます。
コンパイルするには、cscコンパイラへのシェル呼び出しを開始するだけです。パスとスイッチをまっすぐに維持しようとすると頭痛がするかもしれませんが、それは確かに実行できます。
編集:またはより良い、Noldorinが提案したようにCodeDOMを使用してください...
最近、単体テストのためにプロセスを生成する必要がありました。この投稿は、文字列としてのコードまたはプロジェクトからのコードのいずれかを使用してそれを行う単純なクラスを作成したので役に立ちました。このクラスを構築するには、ICSharpCode.Decompiler
およびMicrosoft.CodeAnalysis
NuGetパッケージが必要です。ここにクラスがあります:
using ICSharpCode.Decompiler;
using ICSharpCode.Decompiler.CSharp;
using ICSharpCode.Decompiler.TypeSystem;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
public static class CSharpRunner
{
public static object Run(string snippet, IEnumerable<Assembly> references, string typeName, string methodName, params object[] args) =>
Invoke(Compile(Parse(snippet), references), typeName, methodName, args);
public static object Run(MethodInfo methodInfo, params object[] args)
{
var refs = methodInfo.DeclaringType.Assembly.GetReferencedAssemblies().Select(n => Assembly.Load(n));
return Invoke(Compile(Decompile(methodInfo), refs), methodInfo.DeclaringType.FullName, methodInfo.Name, args);
}
private static Assembly Compile(SyntaxTree syntaxTree, IEnumerable<Assembly> references = null)
{
if (references is null) references = new[] { typeof(object).Assembly, typeof(Enumerable).Assembly };
var mrefs = references.Select(a => MetadataReference.CreateFromFile(a.Location));
var compilation = CSharpCompilation.Create(Path.GetRandomFileName(), new[] { syntaxTree }, mrefs, new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
using (var ms = new MemoryStream())
{
var result = compilation.Emit(ms);
if (result.Success)
{
ms.Seek(0, SeekOrigin.Begin);
return Assembly.Load(ms.ToArray());
}
else
{
throw new InvalidOperationException(string.Join("\n", result.Diagnostics.Where(diagnostic => diagnostic.IsWarningAsError || diagnostic.Severity == DiagnosticSeverity.Error).Select(d => $"{d.Id}: {d.GetMessage()}")));
}
}
}
private static SyntaxTree Decompile(MethodInfo methodInfo)
{
var decompiler = new CSharpDecompiler(methodInfo.DeclaringType.Assembly.Location, new DecompilerSettings());
var typeInfo = decompiler.TypeSystem.MainModule.Compilation.FindType(methodInfo.DeclaringType).GetDefinition();
return Parse(decompiler.DecompileTypeAsString(typeInfo.FullTypeName));
}
private static object Invoke(Assembly assembly, string typeName, string methodName, object[] args)
{
var type = assembly.GetType(typeName);
var obj = Activator.CreateInstance(type);
return type.InvokeMember(methodName, BindingFlags.Default | BindingFlags.InvokeMethod, null, obj, args);
}
private static SyntaxTree Parse(string snippet) => CSharpSyntaxTree.ParseText(snippet);
}
これを使用するには、Run
以下のメソッドを呼び出します。
void Demo1()
{
const string code = @"
public class Runner
{
public void Run() { System.IO.File.AppendAllText(@""C:\Temp\NUnitTest.txt"", System.DateTime.Now.ToString(""o"") + ""\n""); }
}";
CSharpRunner.Run(code, null, "Runner", "Run");
}
void Demo2()
{
CSharpRunner.Run(typeof(Runner).GetMethod("Run"));
}
public class Runner
{
public void Run() { System.IO.File.AppendAllText(@"C:\Temp\NUnitTest.txt", System.DateTime.Now.ToString("o") + "\n"); }
}