在 C# 中根据字符串公式进行计算,根据公式的复杂度和性能要求,有多种实现方案。以下为您梳理了从简单到高级的几种常用方法:
- 使用轻量级第三方库(推荐)
对于大多数业务场景,引入成熟的第三方表达式解析库是最高效、最安全的选择。它们不仅支持基本的数学运算,还支持变量替换、自定义函数和条件判断。
NCalc:非常流行的轻量级数学表达式计算库。支持参数绑定和内置数学函数。
//安装 NuGet 包: NCalc
csharp
var expression = new NCalc.Expression("A + B * C");
expression.Parameters["A"] = 2;
expression.Parameters["B"] = 3;
expression.Parameters["C"] = 4;
var result = expression.Evaluate(); // 结果为 14
其他优秀库:如 FLEE(快速表达式执行器)、Jint(支持 JavaScript 语法的解释器)等,它们内置了词法分析器和运行时上下文管理,能够很好地处理复杂的业务规则引擎。
- 使用 .NET 原生 Roslyn 脚本 API(适合复杂逻辑)
如果您的公式本身就是合法的 C# 代码片段,或者需要调用 .NET 框架内的复杂方法,可以使用 Microsoft 提供的 Roslyn 编译器平台。它可以将字符串实时编译为内存中的程序集并执行,无需手动编写完整的类和方法。
优点:支持完整的 C# 语法,包括 LINQ、Lambda 表达式等。
缺点:首次编译有一定性能开销,且需要严格的安全沙箱控制以防止代码注入攻击。
- 使用内置的 DataTable.Compute(适合简单场景)
如果只是进行非常基础的数学运算,可以使用 ADO.NET 中的 DataTable.Compute 方法。
优点:无需引入任何第三方依赖,开箱即用。
缺点:功能受限,不支持复杂的数学函数和自定义逻辑,且存在已知的性能瓶颈和安全隐患,不建议在高频或高安全性要求的场景中使用。
- 动态编译与反射(传统方案)
利用 Microsoft.CSharp.CSharpCodeProvider 在运行时动态生成 C# 代码,编译为内存 Assembly,再通过反射调用计算方法。
注意:这是一种较老的技术,性能较差,且在现代 .NET Core / .NET 5+ 环境中支持有限,目前更推荐使用 Roslyn 替代此方案。
- 纯手写解析器(适合学习与极简需求)
如果不允许使用任何第三方库,且公式仅包含基础的加减乘除和括号,可以手写解析算法。
中缀转后缀(逆波兰表示法 RPN):将日常使用的中缀表达式(如 1+2*(5-1))转换为后缀表达式(如 1 2 5 1 - * +),然后通过栈(Stack)数据结构进行高效求值。这种方法易于理解,且性能极高。
表达式树 (Expression Trees):使用 System.Linq.Expressions 构建表达式树,并调用 Compile() 编译为强类型委托(如 Func),可以实现毫秒级的执行效率与类型安全。
工程实践中的核心注意事项
在实际业务(如薪资系统、财务报表、动态配置规则引擎)中应用公式计算时,还需要特别注意以下几点:
数值精度问题:涉及货币或财务计算时,务必统一使用 decimal 类型而非 double,以防止浮点数运算带来的精度丢失。
安全性(防注入):绝不能直接执行用户随意输入的任意代码(如包含 System.Diagnostics.Process.Start 的恶意字符串)。必须启用白名单机制,仅允许数学运算符和预设的安全函数。
性能优化:对于高频调用的相同公式,建议增加 LRU 缓存,将已编译的表达式或委托缓存起来,避免重复解析和编译。
异常容错:需妥善处理除零异常、变量为空(Null)以及公式语法错误等情况,避免整个计算流程中断。