我有一个存储以下内容的数据库表:
RuleID objectProperty ComparisonOperator TargetValue 1 age 'greater_than' 15 2 username 'equal' 'some_name' 3 tags 'hasAtLeastOne' 'some_tag some_tag2'
现在说我有这些规则的集合:
List<Rule> rules = db.GetRules();
现在我也有一个用户实例:
User user = db.GetUser(....);
我将如何遍历这些规则,并应用逻辑并执行比较等?
if(user.age > 15) if(user.username == "some_name")
由于对象的“年龄”或“用户名”等属性与比较运算符“great_than”和“等于”一起存储在表中,我怎么可能做到这一点?
C# 是一种静态类型的语言,所以不知道如何前进。
这个片段 将规则编译成快速的可执行代码 (使用表达式树)并且不需要任何复杂的 switch 语句:
(编辑: 使用通用方法的完整工作示例 )
public Func<User, bool> CompileRule(Rule r) { var paramUser = Expression.Parameter(typeof(User)); Expression expr = BuildExpr(r, paramUser); // build a lambda function User->bool and compile it return Expression.Lambda<Func<User, bool>>(expr, paramUser).Compile(); }
然后你可以写:
List<Rule> rules = new List<Rule> { new Rule ("Age", "GreaterThan", "21"), new Rule ( "Name", "Equal", "John"), new Rule ( "Tags", "Contains", "C#" ) }; // compile the rules once var compiledRules = rules.Select(r => CompileRule(r)).ToList(); public bool MatchesAllRules(User user) { return compiledRules.All(rule => rule(user)); }
下面是 BuildExpr 的实现:
Expression BuildExpr(Rule r, ParameterExpression param) { var left = MemberExpression.Property(param, r.MemberName); var tProp = typeof(User).GetProperty(r.MemberName).PropertyType; ExpressionType tBinary; // is the operator a known .NET operator? if (ExpressionType.TryParse(r.Operator, out tBinary)) { var right = Expression.Constant(Convert.ChangeType(r.TargetValue, tProp)); // use a binary operation, e.g. 'Equal' -> 'u.Age == 21' return Expression.MakeBinary(tBinary, left, right); } else { var method = tProp.GetMethod(r.Operator); var tParam = method.GetParameters()[0].ParameterType; var right = Expression.Constant(Convert.ChangeType(r.TargetValue, tParam)); // use a method call, e.g. 'Contains' -> 'u.Tags.Contains(some_tag)' return Expression.Call(left, method, right); } }
请注意,我使用 ‘GreaterThan’ 而不是 ‘greater_than’ 等 - 这是因为 ‘GreaterThan’ 是运算符的 .NET 名称,因此我们不需要任何额外的映射。
如果您需要自定义名称,您可以构建一个非常简单的字典,并在编译规则之前翻译所有运算符:
var nameMap = new Dictionary<string, string> { { "greater_than", "GreaterThan" }, { "hasAtLeastOne", "Contains" } };
为简单起见,代码使用 User 类型。您可以将 User 替换为泛型类型 T 以拥有适用于任何类型对象的泛型 Rule 编译器。此外,代码应该处理错误,例如未知的操作员名称。
请注意,甚至在引入表达式树 API 之前,使用 Reflection.Emit 就可以动态生成代码。LambdaExpression.Compile() 方法在幕后使用 Reflection.Emit(您可以使用ILSpy看到这一点)。