C # 코드 조각을 텍스트 파일 (또는 입력 스트림)에 저장하고 동적으로 실행할 수 있는지 궁금합니다. 나에게 제공된 것이 Main () 블록 내에서 잘 컴파일된다고 가정하면이 코드를 컴파일 및 / 또는 실행할 수 있습니까? 성능상의 이유로 컴파일하는 것을 선호합니다.
최소한 구현에 필요한 인터페이스를 정의한 다음 해당 인터페이스를 구현하는 코드 ‘섹션’을 제공 할 수 있습니다.
답변
C # / 모든 정적 .NET 언어에서 가장 좋은 솔루션은 이러한 작업에 CodeDOM 을 사용하는 것입니다. 참고로 다른 주요 목적은 코드 비트 또는 전체 클래스를 동적으로 생성하는 것입니다.
다음 은 재미를 위해 LINQ를 사용하는 LukeH의 블로그 에서 가져온 짧은 예제 입니다.
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();
}
}
}
답변
다른 사람들은 이미 런타임에 코드를 생성하는 방법에 대한 좋은 대답을했기 때문에 두 번째 단락을 다루겠다고 생각했습니다. 나는 이것에 대한 경험이 있으며 그 경험에서 얻은 교훈을 공유하고 싶습니다.
최소한 구현에 필요한 인터페이스를 정의한 다음 해당 인터페이스를 구현하는 코드 ‘섹션’을 제공 할 수 있습니다.
interface
기본 유형으로 사용하면 문제가있을 수 있습니다 . interface
나중에 새로운 단일 메소드를 추가 하면 interface
현재 구현하는 기존의 모든 클라이언트 제공 클래스가 이제 추상이되어 런타임에 클라이언트 제공 클래스를 컴파일하거나 인스턴스화 할 수 없습니다.
이전 인터페이스를 배송한지 약 1 년이 지나고 지원해야하는 많은 “레거시”데이터를 배포 한 후 새로운 방법을 추가 할 때가 왔습니다. 나는 이전 인터페이스에서 상속받은 새로운 인터페이스를 만들었지만이 방법은 사용 가능한 인터페이스를 확인해야하기 때문에 클라이언트 제공 클래스를로드하고 인스턴스화하는 것을 더 어렵게 만들었습니다.
당시 내가 생각한 한 가지 해결책은 실제 클래스를 아래 유형과 같은 기본 유형으로 사용하는 것이 었습니다. 클래스 자체는 추상으로 표시 될 수 있지만 모든 메소드는 빈 가상 메소드 여야합니다 (추상 메소드는 아님). 그런 다음 클라이언트는 원하는 메소드를 대체 할 수 있으며 기존 클라이언트 제공 코드를 무효화하지 않고 기본 클래스에 새 메소드를 추가 할 수 있습니다.
public abstract class BaseClass
{
public virtual void Foo1() { }
public virtual bool Foo2() { return false; }
...
}
이 문제가 적용되는지 여부에 관계없이 코드 기반과 클라이언트 제공 코드 간의 인터페이스 버전을 지정하는 방법을 고려해야합니다.
답변
이것이 유용하다는 것을 알았습니다-컴파일 된 어셈블리가 현재 참조 한 모든 것을 참조하는지 확인하십시오 .C #에서 컴파일하는 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
하나 인 공용 정적 메소드 Get()
가 유형으로 반환되었습니다 StoryDataIds
. 이 메소드를 호출하는 모습은 다음과 같습니다.
var tempType = asm.GetType(className);
var ids = (StoryDataIds)tempType.GetMethod("Get").Invoke(null, null);
경고 : 컴파일이 놀랍게도 매우 느려질 수 있습니다. 비교적 빠른 10 줄의 코드 덩어리는 비교적 빠른 서버에서 2-10 초 안에 보통 우선 순위로 컴파일됩니다. CompileAssemblyFromSource()
웹 요청과 같이 정상적인 성능을 기대하는 어떤 것에도 전화를 연결해서는 안됩니다 . 대신 우선 순위가 낮은 스레드에서 필요한 코드를 사전에 컴파일하고 컴파일이 완료 될 때까지 해당 코드를 준비해야하는 코드를 처리 할 수 있습니다. 예를 들어 배치 작업 프로세스에서 사용할 수 있습니다.
답변
컴파일하려면 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"); }
}