首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> C# >> 關於C#

C# 動態編譯與執行:打造互動式 C# Shell (REPL) 並實現變數共享與物件探索

     編輯:關於C#

核心概念:CSharpCodeProvider 動態編譯

.NET Framework 內建 Microsoft.CSharp.CSharpCodeProvider 可在執行期編譯 C# 字串為組件。關鍵步驟:

  1. 建構 CompilerParameters:GenerateInMemory=true、GenerateExecutable=false、引用必要組件。
  2. CompileAssemblyFromSource 取得 CompilerResults。
  3. 檢查 Errors,成功則 CompiledAssembly.CreateInstance 實例化並透過反射呼叫方法。

簡易 REPL 實作

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 物件注入

為讓動態代碼存取主程式變數,引入 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; }
}

進階應用:OrcShell - 迷你 .NET 物件瀏覽器

在 REPL 基礎上加入指令系統:

支援即時建立型別實例、呼叫方法、檢視屬性,實現「進入 .NET 程式內部」的互動式探索體驗。

技術挑戰與優化

  • 編譯效能:頻繁編譯產生大量動態組件,記憶體增長。解法:AppDomain 隔離卸載、或改用 Roslyn Scripting API (CSharpScript.EvaluateAsync) 獲得更佳效能與 REPL 原生支援。
  • 智慧提示:整合 ICSharpCode.NRefactory 或 Roslyn Completion Service 實現 Tab 補全。
  • 安全性:動態代碼擁有完整信任權限,生產環境需嚴格限制輸入來源或使用沙箱 AppDomain。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved
指令功能
lsc / dirc列出當前命名空間類型與子命名空間
cdc切換當前命名空間 (支援 .. 返回)
my列出所有共享變數
alias列出/設定類型別名
use / unuse引入/移除命名空間 (影響動態編譯 using)
import載入組件 (檔案路徑或部分名稱)