.NET Framework 內建 Microsoft.CSharp.CSharpCodeProvider 可在執行期編譯 C# 字串為組件。關鍵步驟:
CompilerParameters:GenerateInMemory=true、GenerateExecutable=false、引用必要組件。CompileAssemblyFromSource 取得 CompilerResults。Errors,成功則 CompiledAssembly.CreateInstance 實例化並透過反射呼叫方法。public class CSharpRepl {
readonly CSharpCodeProvider _provider = new CSharpCodeProvider(new Dictionary{{"CompilerVersion","v3.5"}});
readonly CompilerParameters _params;
readonly Dictionary _referencedAssemblies = new();
public CSharpRepl() {
_params = new CompilerParameters { GenerateInMemory = true, GenerateExecutable = false };
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
if (!asm.IsDynamic && !string.IsNullOrEmpty(asm.Location))
_params.ReferencedAssemblies.Add(asm.Location);
AppDomain.CurrentDomain.AssemblyLoad += (s, e) => {
var a = e.LoadedAssembly;
if (!_referencedAssemblies.ContainsKey(a.FullName)) {
_referencedAssemblies[a.FullName] = a;
_params.ReferencedAssemblies.Add(a.Location);
}
};
}
public void Run(string code) {
string fullCode = Wrap(code);
var results = _provider.CompileAssemblyFromSource(_params, fullCode);
if (results.Errors.HasErrors) {
// 嘗試自動修正:將運算式包裝為變數輸出
if (results.Errors.Cast().All(e => e.ErrorNumber == "CS0201")) {
string tempVar = $"__temp_{DateTime.Now.Ticks}";
fullCode = Wrap($"var {tempVar} = {code}; Console.WriteLine({tempVar});");
results = _provider.CompileAssemblyFromSource(_params, fullCode);
}
if (results.Errors.HasErrors) {
foreach (var err in results.Errors) Console.WriteLine($"Error {err.ErrorNumber}: {err.ErrorText}");
return;
}
}
var asm = results.CompiledAssembly;
var obj = asm.CreateInstance("Repl.DynamicClass");
var method = asm.GetType("Repl.DynamicClass").GetMethod("Execute");
method.Invoke(obj, null);
}
string Wrap(string body) => $@"
using System;
namespace Repl {{
public class DynamicClass {{
public void Execute() {{
{body}
}}
}}
}}";
}
為讓動態代碼存取主程式變數,引入 Context 物件並傳遞給動態方法:
public class ReplContext {
public SortedDictionary Variables { get; } = new();
public object this[string name] {
get => Variables.TryGetValue(name, out var v) ? v : null;
set { Variables.Remove(name); Variables.Add(name, value); }
}
}
// 動態代碼包裝器修改為接收 Context
string Wrap(string body) => $@"
using System;
using Repl;
namespace Repl {{
public class DynamicClass {{
public void Execute(ReplContext ctx) {{
{Preprocess(body, ctx)}
}}
}}
}}";
$x = 123 → 定義/賦值變數 x。Console.WriteLine($x) → 讀取變數 x。$[A-Za-z_][A-Za-z0-9_]*,首次出現且在行首視為定義,產生區域變數並存入 ctx["x"];後續出現替換為 (new ObjectHelper<T>(ctx, "x")).Value。public class ObjectHelper {
readonly ReplContext _ctx; readonly string _name;
public ObjectHelper(ReplContext ctx, string name) { _ctx = ctx; _name = name; }
public T Value { get => (T)_ctx[_name]; set => _ctx[_name] = value; }
}
在 REPL 基礎上加入指令系統:
| 指令 | 功能 |
|---|---|
| lsc / dirc | 列出當前命名空間類型與子命名空間 |
| cdc | 切換當前命名空間 (支援 .. 返回) |
| my | 列出所有共享變數 |
| alias | 列出/設定類型別名 |
| use / unuse | 引入/移除命名空間 (影響動態編譯 using) |
| import | 載入組件 (檔案路徑或部分名稱) |