.NET Core/5+에서 C# 코드를 동적으로 컴파일/사용하는 방법
현재, .NET Core/5+에서 CodeDomProvider는 사용할 수 없습니다. 그런데 이유는 알 수 없지만, "System.CodeDom" 어셈블리를 .NET Core/5+ 프로젝트에서 참조할 수 있게 제한을 풀었기 때문에 nuget으로 설치는 됩니다.
Install-Package System.CodeDom
/*
.NETFramework 4.6.1
No dependencies.
.NETStandard 2.0
No dependencies.
*/
하지만 실제로 사용해 보면,
using System.CodeDom.Compiler;
string srcContents = System.IO.File.ReadAllText("dynamic_code.txt");
CodeDomProvider codeProvider = CodeDomProvider.CreateProvider("CSharp");
CompilerInfo langCompilerInfo = CodeDomProvider.GetCompilerInfo("CSharp");
System.CodeDom.Compiler.CompilerParameters parameters = langCompilerInfo.CreateDefaultCompilerParameters();
parameters.IncludeDebugInformation = true;
parameters.CompilerOptions = "/define:DEBUG;TRACE";
codeProvider.CompileAssemblyFromSource(parameters, srcContents);
PlatformNotSupportedException 예외가 발생합니다.
Unhandled exception. System.PlatformNotSupportedException: Operation is not supported on this platform.
at Microsoft.CSharp.CSharpCodeGenerator.FromFileBatch(CompilerParameters options, String[] fileNames) in System.CodeDom.dll:token 0x600059e+0x5
at Microsoft.CSharp.CSharpCodeGenerator.FromSourceBatch(CompilerParameters options, String[] sources) in System.CodeDom.dll:token 0x6000597+0x8f
at Microsoft.CSharp.CSharpCodeGenerator.System.CodeDom.Compiler.ICodeCompiler.CompileAssemblyFromSourceBatch(CompilerParameters options, String[] sources) in System.CodeDom.dll:token 0x600058f+0xf
at System.CodeDom.Compiler.CodeDomProvider.CompileAssemblyFromSource(CompilerParameters options, String[] sources) in System.CodeDom.dll:token 0x60002e5+0x0
at <Program>$.<Main>$(String[] args)
검색해 보면, "Microsoft.CodeDom.Providers.DotNetCompilerPlatform"을 사용해 보라고도 하는데, 이것은 .NET Framework에서만 설치되므로 .NET Core/5+에서는 사용 자체가 안 됩니다.
어쩔 수 없습니다. Rosyln과 연동하는 Microsoft.CodeAnalysis.CSharp을 사용해,
Install-Package Microsoft.CodeAnalysis.CSharp
다음의 예제에서처럼,
joelmartinez/dotnet-core-roslyn-sample
; https://github.com/joelmartinez/dotnet-core-roslyn-sample/blob/master/Program.cs
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Emit;
using System;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Loader;
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
string codeToCompile = @"
using System;
using System.Text;
public class MyType
{
public void Print(object obj)
{
StringBuilder sb = new StringBuilder();
sb.Append(
DateTime.Now
);
Console.WriteLine(""Hello: "" + obj + "" : "" + sb.ToString());
}
}
";
string assemblyName = "MyAssembly";
SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(codeToCompile);
var refPaths = new[] {
typeof(System.Object).GetTypeInfo().Assembly.Location,
typeof(Console).GetTypeInfo().Assembly.Location,
Path.Combine(Path.GetDirectoryName(typeof(System.Runtime.GCSettings).GetTypeInfo().Assembly.Location), "System.Runtime.dll")
};
MetadataReference[] references = refPaths.Select(r => MetadataReference.CreateFromFile(r)).ToArray();
CSharpCompilationOptions options = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary);
CSharpCompilation compilation = CSharpCompilation.Create(
assemblyName,
syntaxTrees: new[] { syntaxTree },
references: references,
options: options);
using (var ms = new MemoryStream())
{
EmitResult result = compilation.Emit(ms);
if (result.Success)
{
ms.Seek(0, SeekOrigin.Begin);
Assembly assembly = AssemblyLoadContext.Default.LoadFromStream(ms);
var type = assembly.GetType("MyType");
var instance = assembly.CreateInstance("MyType");
var meth = type.GetMember("Print").First() as MethodInfo;
meth.Invoke(instance, new[] { "World" });
}
}
}
}
}
소스 코드로부터 동적으로 컴파일을 해 어셈블리를 얻을 수 있습니다. 따라서, 만약 .NET Framework/.NET Core/5+를 지원해야 하는 공통 소스 코드가 있다면 동적 컴파일 부분만큼은 별도의 클래스를 사용하도록 나눠야 합니다.
(
첨부 파일은 이 글의 예제 코드를 포함합니다.)
[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]