動態編譯** = 在程式運行期間,將 C# 代碼字串編譯成組件,再透過反射調用其中的成員。類似 SQL EXEC('...') 或 JS eval(),但具備強型別、編譯期檢查、IDE 支援等優勢。
"2+3*(4-1)/5%7")注意:編譯組件無法卸載、首次編譯較慢,**僅適用於「高頻重複呼叫」的熱路徑**。一次性計算請用 DataTable.Compute 或表達式樹。
using Microsoft.CSharp;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
public class DynamicCompile_1 {
public static Assembly CompileAssembly(string code, params Type[] usingTypes) {
var cp = new CompilerParameters {
GenerateExecutable = false,
GenerateInMemory = true
};
var ns = new HashSet<string>();
foreach (var t in usingTypes) {
ns.Add("using " + t.Namespace + ";" + Environment.NewLine);
cp.ReferencedAssemblies.Add(t.Module.FullyQualifiedName);
}
code = string.Concat(ns) + code; // 自動補 using
using (var provider = new CSharpCodeProvider()) {
var cr = provider.CompileAssemblyFromSource(cp, code);
if (cr.Errors.HasErrors) {
var sb = new StringBuilder("編譯錯誤:");
foreach (CompilerError err in cr.Errors) sb.AppendLine(err.ErrorText);
throw new Exception(sb.ToString());
}
return cr.CompiledAssembly;
}
}
}
public static decimal Calculate(string formula) {
string code = $@"
public class Class1 {{
public static decimal GetValue() {{
return (decimal)({formula});
}}
}}";
// 傳入公式用到的型別(decimal)
Type type = DynamicCompile_1.CompileAssembly(code, typeof(decimal))
.GetType("Class1");
return (decimal)type.GetMethod("GetValue").Invoke(null, null);
}
// 測試
var result = Calculate("2 + 3 * (4 - 1) / 5 % 7"); // = 2
Console.WriteLine(result);
| 項目 | 說明 |
|---|---|
GenerateInMemory = true | 不產生實體 DLL,直接在記憶體組件 |
ReferencedAssemblies | 必須加入代碼用到的所有型別所在組件 |
自動補 using | 用 HashSet 去重命名空間,避免重複 |
| 錯誤處理 | CompilerResults.Errors 收集編譯錯誤並拋出 |
System.CodeDom.CompilerMicrosoft.CodeAnalysis.CSharp.Scripting (Roslyn) 或 System.CodeDom 相容包這就是動態編譯的「Hello World」。下一篇將實戰:手寫 JSON 序列化器,對比反射與動態編譯效能。