在NRules中实现正向推理:基于前置规则结果执行且免额外类
我明白你不想为每个规则都写一个新类的痛点——这种硬编码的方式不仅繁琐,还会让代码库变得臃肿。其实可以用数据驱动的通用规则模型来实现正向推理,不用每个规则都搞一个专属类。下面我给你详细拆解这个方案:
核心思路
正向推理的本质是:维护一个事实库,循环匹配所有规则的触发条件,执行满足条件的规则以生成新事实,直到没有新事实可以添加为止。我们可以把每个规则的「条件判断」和「执行动作」封装成可配置的委托,而不是硬编码成类,这样所有规则都可以复用同一个通用规则结构。
具体实现步骤
1. 定义事实模型
首先需要一个通用的Fact类来存储事实的类型和值,方便引擎识别和匹配:
public class Fact { public string Type { get; set; } // 事实类型,比如"CustomerType"、"OrderTotal" public object Value { get; set; } // 事实的值 // 重写Equals和GetHashCode,避免重复添加相同事实导致死循环 public override bool Equals(object obj) { return obj is Fact fact && Type == fact.Type && EqualityComparer<object>.Default.Equals(Value, fact.Value); } public override int GetHashCode() { return HashCode.Combine(Type, Value); } }
2. 定义通用规则结构
不用继承Rule类,而是创建一个GenericRule类,用委托来承载规则的条件和动作:
public class GenericRule { public string Name { get; set; } // 规则名称,方便调试 // 条件:判断当前事实集合是否满足规则触发要求 public Func<IEnumerable<Fact>, bool> Condition { get; set; } // 动作:执行规则逻辑,生成新事实并添加到传入的列表中 public Action<IEnumerable<Fact>, List<Fact>> Action { get; set; } }
3. 实现正向推理引擎
引擎负责维护事实库和规则列表,核心逻辑是循环触发规则直到没有新事实:
public class ForwardChainingEngine { private List<Fact> _facts = new List<Fact>(); private List<GenericRule> _rules = new List<GenericRule>(); // 添加初始事实 public void AddFact(Fact fact) { if (!_facts.Contains(fact)) { _facts.Add(fact); } } // 添加规则 public void AddRule(GenericRule rule) { _rules.Add(rule); } // 启动正向推理 public void Run() { bool hasNewFacts; do { hasNewFacts = false; List<Fact> newFacts = new List<Fact>(); // 遍历所有规则,检查是否满足条件 foreach (var rule in _rules) { if (rule.Condition(_facts)) { rule.Action(_facts, newFacts); } } // 将新事实加入库中,标记是否有更新 foreach (var fact in newFacts) { if (!_facts.Contains(fact)) { _facts.Add(fact); hasNewFacts = true; } } } while (hasNewFacts); // 直到没有新事实产生为止 } // 辅助方法:根据类型获取事实值 public T GetFactValue<T>(string factType) { var fact = _facts.FirstOrDefault(f => f.Type == factType); return fact != null ? (T)fact.Value : default; } }
示例:替代类式规则
你提到的PreferredCustomerDiscountRule,现在可以直接用GenericRule实例实现,完全不用新建类:
// 初始化推理引擎 var engine = new ForwardChainingEngine(); // 添加初始事实:优选客户、订单总额1000元 engine.AddFact(new Fact { Type = "CustomerType", Value = "Preferred" }); engine.AddFact(new Fact { Type = "OrderTotal", Value = 1000.0 }); // 定义优选客户折扣规则(不用新建类) var preferredDiscountRule = new GenericRule { Name = "PreferredCustomerDiscountRule", Condition = facts => { var customerType = facts.FirstOrDefault(f => f.Type == "CustomerType")?.Value as string; var orderTotal = facts.FirstOrDefault(f => f.Type == "OrderTotal")?.Value as double?; return customerType == "Preferred" && orderTotal >= 500; }, Action = (facts, newFacts) => { var orderTotal = (double)facts.First(f => f.Type == "OrderTotal").Value; var discount = orderTotal * 0.1; // 10%折扣 newFacts.Add(new Fact { Type = "DiscountAmount", Value = discount }); } }; // 再添加一个链式规则:折扣>=50元时额外减10元 var extraDiscountRule = new GenericRule { Name = "ExtraDiscountRule", Condition = facts => { var discount = facts.FirstOrDefault(f => f.Type == "DiscountAmount")?.Value as double?; return discount >= 50; }, Action = (facts, newFacts) => { var currentDiscount = (double)facts.First(f => f.Type == "DiscountAmount").Value; newFacts.Add(new Fact { Type = "DiscountAmount", Value = currentDiscount + 10 }); } }; // 将规则加入引擎并运行 engine.AddRule(preferredDiscountRule); engine.AddRule(extraDiscountRule); engine.Run(); // 获取最终折扣结果 var finalDiscount = engine.GetFactValue<double>("DiscountAmount"); Console.WriteLine($"最终折扣金额:{finalDiscount}"); // 输出110.0
方案优势与扩展
- 无需新增类:所有规则都通过
GenericRule实例定义,避免类爆炸 - 动态性强:规则可以动态添加、修改,甚至从配置文件加载(如果把条件/动作转换成可序列化的表达式字符串)
- 可扩展性:可以给
GenericRule添加Priority属性控制规则执行顺序,或者增加规则冲突检测逻辑 - 职责分离:推理引擎负责核心逻辑,规则只关注自己的条件和动作,代码更清晰
内容的提问来源于stack exchange,提问作者Kishore
相关产品推荐
相关产品推荐

