前三篇已掌握 CSharpCodeProvider 基礎用法、實戰 JSON 序列化、拋棄反射改用介面零反射調用。本文將其封裝為靜態類別 DynamicCompile,提供三個高階 API 覆蓋 90% 場景,並內建命名空間去重、除錯資訊關閉、泛型委派回傳型別名稱解析等細節優化。
public static class DynamicCompile {
// 1. 編譯類別 → 回傳 Type(適用靜態類別、單例模式)
public static Type CompileClass(string code, params Type[] usingTypes);
// 2. 編譯類別 → 回傳實例(適用有無參建構子的類別)
public static object CompileObject(string code, params Type[] usingTypes);
// 3. 編譯方法 → 回傳強型別委派(最常用、效能最佳)
public static T CompileMethod<T>(string code, params Type[] usingTypes) where T : Delegate;
}
動態編譯出的方法無法直接轉委派,作者巧妙包裝:
ICloneable 的類別中Clone() 回傳 (T)methodName,隱式轉換為委派CompileObject 取得實例 → ((ICloneable)obj).Clone() 即得強型別委派private const string FORMAT_CLASS = @"
public class %ClassName : ICloneable {
object ICloneable.Clone() { return (%Type)%MethodName; }
%Method
}";
public static T CompileMethod<T>(string code, params Type[] usingTypes) {
var m = Regex_Method.Match(code);
if (!m.Success) throw new ArgumentException("code 參數格式錯誤");
code = FORMAT_CLASS
.Replace("%ClassName", "_" + Guid.NewGuid().ToString("N"))
.Replace("%Type", GetTypeDisplayName(typeof(T)))
.Replace("%MethodName", m.Groups["n"].Value)
.Replace("%Method", code);
var obj = CompileObject(code, usingTypes);
return (T)((ICloneable)obj).Clone();
}
// 舊寫法:手動拼類別、反射取 MethodInfo、Invoke
public decimal GetValue(string formula) {
string code = $@"public class Class1 {{ public static decimal GetValue() {{ return (decimal)({formula}); }} }}";
var ass = DynamicCompile_1.CompileAssembly(code, typeof(decimal));
return (decimal)ass.GetType("Class1").GetMethod("GetValue").Invoke(null, null);
}
// 新寫法:只寫方法本體,直接得委派
public decimal GetValue(string formula) {
string code = $@"decimal GetValue() {{ return (decimal)({formula}); }}";
var func = DynamicCompile.CompileMethod<Func<decimal>>(code, typeof(decimal));
return func(); // 零反射,直接調用
}
真實產生的代碼:
using System;
public class _98b6ede1ea204541bc4e709932e6c993 : ICloneable {
object ICloneable.Clone() { return (System.Func<System.Decimal>)GetValue; }
decimal GetValue() { return (decimal)(1+2+3+4+5*6); }
}
using Microsoft.CSharp;
using System;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
namespace blqw {
public static class DynamicCompile {
private static readonly Regex Regex_Method = new(
@"^\s*[\w\s]*\s(?\w+)\s*\(([^)]*)\)\s*\{[^}]*\}\s*$",
RegexOptions.Compiled | RegexOptions.IgnoreCase);
private const string FORMAT_CLASS = @"
public class %ClassName : ICloneable {
object ICloneable.Clone() { return (%Type)%MethodName; }
%Method
}";
public static Type CompileClass(string code, params Type[] usingTypes)
=> CompileAssembly(code, usingTypes).GetTypes()[0];
public static object CompileObject(string code, params Type[] usingTypes)
=> CompileAssembly(code, usingTypes).GetTypes()[0].GetConstructors()[0].Invoke(null);
public static T CompileMethod<T>(string code, params Type[] usingTypes) where T : Delegate {
var m = Regex_Method.Match(code);
if (!m.Success) throw new ArgumentException("code 參數格式錯誤");
code = FORMAT_CLASS
.Replace("%ClassName", "_" + Guid.NewGuid().ToString("N"))
.Replace("%Type", GetTypeDisplayName(typeof(T)))
.Replace("%MethodName", m.Groups["n"].Value)
.Replace("%Method", code);
var obj = CompileObject(code, usingTypes);
return (T)((ICloneable)obj).Clone();
}
private static string GetTypeDisplayName(Type type) {
if (type == null) return "null";
if (!type.IsGenericType) return type.FullName;
var args = type.GetGenericArguments();
string gname = type.GetGenericTypeDefinition().FullName;
gname = gname.Remove(gname.IndexOf('`'));
if (args.Length == 1) return gname + "<" + GetTypeDisplayName(args[0]) + ">";
var sb = new StringBuilder(gname).Append("<");
foreach (var a in args) { sb.Append(GetTypeDisplayName(a)).Append(","); }
sb[sb.Length - 1] = '>';
return sb.ToString();
}
public static Assembly CompileAssembly(string code, params Type[] usingTypes) {
var cp = new CompilerParameters {
GenerateExecutable = false,
GenerateInMemory = true,
IncludeDebugInformation = false
};
var ns = new Dictionary<string, bool>();
foreach (var t in usingTypes) {
ns["using " + t.Namespace + ";" + Environment.NewLine] = true;
cp.ReferencedAssemblies.Add(t.Module.FullyQualifiedName);
}
code = string.Concat(ns.Keys) + code;
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;
}
}
}
}
| 缺陷 | 影響 | 對策 |
|---|---|---|
| 組件無法卸載 | 每次編譯永久佔用記憶體 | 只編譯「高頻重複呼叫」的熱路徑;冷路徑用反射 |
| 首次編譯慢 | 比反射慢數倍 | 啟動時預編譯、快取委派、搭配 ConcurrentDictionary |
| 難以除錯 | 動態代碼無法設中斷點 | 先用靜態代碼驗證邏輯,成熟後再動態化 |
封裝完成的 DynamicCompile 已在作者工具庫服役多年,後續 JSON 轉換器、資料實體處理皆建立於此。源碼與 Demo 見文末連結。