面對 BOM 物料數量聯動、用戶自定義計算邏輯等場景,本文實現一個動態公式計算引擎:用戶編寫類 C# 表達式(如 return [物料B] + [物料D];),引擎運行時解析、生成 IL、編譯執行,實現毫秒級響應。
CParameter 封裝鍵值、類型、公式字符串、引用參數列表。IFormulaCalculate 定義 Calculate() 方法,動態生成實現類。BaseCTemplate 彙總所有參數,構造時實例化公式對象並注入依賴。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;
}
用戶公式: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]);
}
}
}
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,避免重複編譯。if、循環、自定義函數庫(實現 IFormulaCodeParse 擴展)