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

動態自定義公式計算引擎:基於 CodeDOM 的運行時代碼生成方案

     編輯:關於.NET

面對 BOM 物料數量聯動、用戶自定義計算邏輯等場景,本文實現一個動態公式計算引擎:用戶編寫類 C# 表達式(如 return [物料B] + [物料D];),引擎運行時解析、生成 IL、編譯執行,實現毫秒級響應。

核心架構

  1. 參數模型:CParameter 封裝鍵值、類型、公式字符串、引用參數列表。
  2. 公式接口:IFormulaCalculate 定義 Calculate() 方法,動態生成實現類。
  3. 模板類:BaseCTemplate 彙總所有參數,構造時實例化公式對象並注入依賴。
  4. CodeDOM 生成器:將公式字符串轉換為 C# 代碼樹,動態編譯為程序集。

動態代碼生成流程

1. 公式解析與代碼生成

private static CodeCompileUnit BuildCodeUnit(string tplName, IList<CParameter> params, IFormulaCodeParse parser) {
    var unit = new CodeCompileUnit();
    unit.Namespaces.Add(BuildFormulaNamespace(tplName, params, parser));
    unit.Namespaces.Add(BuildTemplateNamespace(tplName, params, parser));
    return unit;
}

private static CodeNamespace BuildFormulaNamespace(...) {
    var ns = new CodeNamespace("DynamicCalculate.Formula");
    // 引入必要命名空間
    foreach (var p in params.Where(p => !string.IsNullOrEmpty(p.FormulaStr))) {
        var cls = new CodeTypeDeclaration($"C_{tplName}_Formula_{p.Key}") { BaseTypes = { typeof(IFormulaCalculate) } };
        // 構造函數注入參數列表
        // 字段/屬性 _ParamList / ParamList
        // Calculate 方法:parser.GetCompilerCode(p.FormulaStr, p.RefParams) 生成方法體
        ns.Types.Add(cls);
    }
    return ns;
}

2. 生成代碼示例

用戶公式:return [Key1] + 10;、return [Key2] + [Key1];

生成類:

namespace DynamicCalculate.Formula {
    public class C_testT_Formula_Key1 : IFormulaCalculate {
        private IList<CParameter> _ParamList;
        public C_testT_Formula_Key1(IList<CParameter> paramList) { _ParamList = paramList; }
        public object Calculate() => Convert.ToInt64(_ParamList[0].PValue) + 10;
    }
    public class C_testT_Formula_Key2 : IFormulaCalculate {
        public object Calculate() => Convert.ToInt64(_ParamList[0].PValue) + Convert.ToInt64(_ParamList[1].PValue);
    }
}
namespace DynamicCalculate.Template {
    public class C_testT : BaseCTemplate {
        public C_testT(string name, IList<CParameter> params) : base(name) {
            var f1 = new C_testT_Formula_Key1(params[0].RefParams);
            params[0].Formula = f1; _Paramters.Add(params[0]);
            var f2 = new C_testT_Formula_Key2(params[1].RefParams);
            params[1].Formula = f2; _Paramters.Add(params[1]);
        }
    }
}

3. 模板工廠與執行

public static ICTemplate BuildTemplate(string name, IList<CParameter> params) {
    var unit = BuildCodeUnit(name, params, new DefaultFormulaParser());
    var provider = new CSharpCodeProvider();
    var results = provider.CompileAssemblyFromDom(new CompilerParameters { GenerateInMemory = true }, unit);
    var tplType = results.CompiledAssembly.GetType($"DynamicCalculate.Template.C_{name}");
    return (ICTemplate)Activator.CreateInstance(tplType, name, params);
}

// 調用
var tpl = CTemplateFactory.BuildTemplate("testT", paramList);
tpl.CalculateParameters(ref paramList);  // 觸發鏈式計算

單元測試驗證

[TestMethod]
public void TestFormulaChain() {
    var list = new List<CParameter> {
        new CParameter { Key="[Key1]", PValue=1, FormulaStr="return [Key1] + 10" },
        new CParameter { Key="[Key2]", PValue=1, FormulaStr="return [Key2] + [Key1]" }
    };
    var tpl = CTemplateFactory.BuildTemplate("testT", list);
    tpl.CalculateParameters(ref list);
    Assert.AreEqual(11, list[0].PValue);  // 1 + 10
    Assert.AreEqual(12, list[1].PValue);  // 1 + 11
}

關鍵技術點

  • 參數引用解析:IFormulaCodeParse 將 [Key] 轉換為 _ParamList[index].PValue,支援強類型轉換。
  • 依賴注入:模板構造時將 RefParams(僅含被引用參數)傳給公式構造函數,減少上下文大小。
  • 內存編譯:GenerateInMemory=true 避免磁盤 I/O,毫秒級編譯完成。
  • 緩存優化:同一模板名稱可緩存編譯後的 Assembly,避免重複編譯。

適用場景與擴展

  • BOM 物料數量聯動計算
  • 報表動態指標定義
  • 規則引擎條件表達式
  • 擴展支援:條件判斷 if、循環、自定義函數庫(實現 IFormulaCodeParse 擴展)
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved