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

C# 線上 IDE 原型:動態編譯執行與 Console 輸出捕獲

     編輯:關於C#

專案緣起

朋友電腦未安裝 Visual Studio,需快速驗證幾十行 C# 程式碼。靈感來自線上編譯器:瀏覽器輸入代碼 → 伺服器動態編譯執行 → 回傳結果。本文實作一個**最小可行原型**,展示核心技術:CSharpCodeProvider、CompilerParameters、Console.WriteLine 重導向。

架構圖解

瀏覽器 (前端)
   │  HTTP POST { code, id }
   ▼
ASP.NET / Web API (後端)
   │  1. 字串替換 Console.Write → Consoler.Write
   │  2. CSharpCodeProvider 編譯成記憶體組件
   │  3. 反射呼叫 Program.Main()
   │  4. 從 Consoler.Outputs[id] 取回輸出
   ▼
JSON 回傳 { output: [...] }

核心類別實作

1. 輸出捕獲器 Consoler

public static class Consoler
{
    // 以 Session/Request ID 隔離多用戶輸出
    public static Dictionary> Outputs { get; } = new Dictionary>();

    public static void Write(string id, object value)
    {
        if (!Outputs.ContainsKey(id)) Outputs[id] = new List();
        Outputs[id].Add(value?.ToString() ?? "");
    }
    public static void WriteLine(string id, object value) => Write(id, (value?.ToString() ?? "") + "\n");

    public static List TakeOutput(string id)
    {
        if (Outputs.TryGetValue(id, out var list))
        {
            Outputs.Remove(id);
            return list;
        }
        return new List();
    }
}

2. 動態編譯執行器 CodeRunner

public class CodeRunner
{
    private readonly string _frameworkVersion; // "v2.0", "v3.5", "v4.0"...

    public CodeRunner(string frameworkVersion = "v4.0") => _frameworkVersion = frameworkVersion;

    public List Run(string code, string requestId, params string[] extraAssemblies)
    {
        // 1. 註冊輸出容器
        Consoler.Outputs[requestId] = new List();

        // 2. 編譯參數
        var cp = new CompilerParameters
        {
            GenerateInMemory = true,           // 直接在記憶體產生組件
            TreatWarningsAsErrors = false,
            CompilerOptions = "/target:library /optimize",
            IncludeDebugInformation = false
        };
        cp.ReferencedAssemblies.AddRange(new[] { "mscorlib.dll", "System.dll", "System.Core.dll" });
        foreach (var asm in extraAssemblies) cp.ReferencedAssemblies.Add(asm);
        // 參考自身組件 (包含 Consoler)
        cp.ReferencedAssemblies.Add(typeof(Consoler).Assembly.Location);

        // 3. 代碼預處理:重導向 Console 輸出
        code = code.Replace("Console.WriteLine", $"Consoler.WriteLine(\"{requestId}\", ")
                   .Replace("Console.Write", $"Consoler.Write(\"{requestId}\", ")
                   .Replace("};", "});"); // 修正 Lambda 結尾 (簡易處理)

        // 4. 編譯
        var provider = new CSharpCodeProvider(new Dictionary { { "CompilerVersion", _frameworkVersion } });
        var results = provider.CompileAssemblyFromSource(cp, code);

        if (results.Errors.HasErrors)
        {
            foreach (CompilerError err in results.Errors)
                Consoler.WriteLine(requestId, $"錯誤 {err.Line}: {err.ErrorText}");
            return Consoler.TakeOutput(requestId);
        }

        // 5. 執行 Program.Main()
        try
        {
            var asm = results.CompiledAssembly;
            var entry = asm.GetType("Program")?.GetMethod("Main", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic);
            entry?.Invoke(null, null);
        }
        catch (Exception ex)
        {
            Consoler.WriteLine(requestId, $"執行期錯誤: {ex.Message}");
        }

        return Consoler.TakeOutput(requestId);
    }
}

3. ASP.NET Core Controller 範例

[ApiController]
[Route("api/[controller]")]
public class CompileController : ControllerBase
{
    private readonly CodeRunner _runner = new CodeRunner("v4.0");

    [HttpPost("run")]
    public IActionResult Run([FromBody] CompileRequest req)
    {
        if (string.IsNullOrWhiteSpace(req.Code)) return BadRequest("代碼不可為空");
        var id = Guid.NewGuid().ToString("N");
        var output = _runner.Run(req.Code, id, req.References);
        return Ok(new { output });
    }
}

public class CompileRequest
{
    public string Code { get; set; }
    public string[] References { get; set; } = Array.Empty();
}

安全性與生產化待辦 (重要)

風險對策
任意程式碼執行 (RCE)沙箱隔離 (Docker / gVisor / AppContainer)、限制 CPU/記憶體/網路/檔案系統
惡意無限迴圈執行緒超時強制中止 (需獨立 Process 而非 AppDomain)
敏感資訊洩露禁止引用系統組件、過濾 Process/File/Registry 等危險 API
編譯器版本相容Roslyn (Microsoft.CodeAnalysis.CSharp.Scripting) 取代舊版 CodeDom

現代替代方案:Roslyn Scripting API

// .NET Core / .NET 5+ 推薦用法
using Microsoft.CodeAnalysis.CSharp.Scripting;
using Microsoft.CodeAnalysis.Scripting;

var options = ScriptOptions.Default
    .AddReferences("System.dll", "System.Core.dll")
    .AddImports("System", "System.Linq", "System.Collections.Generic");

var result = await CSharpScript.RunAsync(@"
    var list = Enumerable.Range(1, 10).ToList();
    Console.WriteLine(string.Join(',', list));
", options);

Roslyn 提供**更快的編譯**、**更豐富的診斷**、**支援 C# 9/10/11 語法**,且可輕鬆實作 REPL 互動式環境。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved