多数C#项目无需独立规则引擎,用switch、Dictionary或策略类即可覆盖80%场景;真正需要规则引擎仅当规则由非开发人员动态配置、需热更新、存在大量依赖判断或频繁变更且需版本追溯。

为什么别急着集成 RuleEngine 或 NRules
多数 C# 项目压根不需要独立规则引擎——用 switch 表达式、Dictionary 或简单策略类就能覆盖 80% 的业务校验、路由、状态转换场景。过早引入 NRules、WorkflowCore 或 Microsoft.RuleEngine(已废弃)反而增加调试难度和部署负担。
真正需要规则引擎的信号只有几个:
• 规则由非开发人员(如运营)动态配置,且需热更新
• 同一输入要触发几十条相互依赖的条件判断(如风控评分卡)
• 规则逻辑频繁变更,且版本需审计追溯
NRules 入门最简路径:从 Session 和 Rule 开始
NRules 是目前 C# 生态中维护活跃、DSL 清晰的规则引擎。它不支持运行时编辑规则,但编译期定义足够轻量。
- 必须继承
Rule类,用[Name]和[Description]标注可读性 - 条件写在
public override void Define()里,用When()+Match;动作写在() Then()里 - 执行前必须调用
session.Insert(obj)插入事实(fact),否则规则不触发 - 默认不自动循环匹配,需显式调用
session.Fire()—— 忘了这句是新手最常踩的坑
public class AgeEligibilityRule : Rule
{
public override void Define()
{
Person person = null;
When()
.Match<person>(p => p != null && p.Age >= 18);
Then()
.Do(ctx => Console.WriteLine($"{person.Name} is eligible"));
}
}</person>
用 Expression> 实现动态规则更可控
如果规则来自 JSON 配置或数据库字段(比如 "Age > 18 && Status == 'Active'"),硬编码 NRules 就不现实。此时直接解析表达式比引入完整引擎更稳妥。
- 用
System.Linq.Expressions手动构建树,或借助System.Linq.Dynamic.Core库 -
DynamicExpressionParser.ParseLambda能把字符串转成委托,性能损耗可控(首次编译慢,后续缓存)(ruleString) - 注意:用户输入的表达式必须严格白名单校验,禁止访问
GetType()、Assembly等敏感成员,否则有远程代码执行风险 - 别用
Eval()或CompileToMethod()—— 它们无法在 .NET Core/.NET 5+ 的 AOT 或某些容器环境工作
Microsoft.Extensions.DependencyInjection 怎么注入规则集合
规则不是单例,而是按业务域分组(如 OrderValidationRules、RefundPolicyRules)。靠 DI 容器管理生命周期最自然。
- 注册时用
services.AddSingleton>(sp => new IRule[] { new OrderAmountRule(), new StockRule() }) - 避免用
AddTransient—— 每次 Resolve 都新建实例,规则间状态(如计数器)无法共享 - 若规则需访问数据库或 HTTP 客户端,构造函数参数必须声明为
IServiceProvider并延迟解析,防止构造时 DI 循环依赖 - 调用方接收
IEnumerable后,自己控制执行顺序和中断逻辑(比如某个规则返回false就跳过后续)
规则引擎真正的复杂点不在语法或 API,而在于「规则如何与主业务流程解耦又不失上下文」——比如一个订单创建流程里,风控规则失败该抛异常还是降级为日志?是否允许部分规则静默失败?这些决策比选哪个库重要得多。










