这段代码将规则编译成快速可执行的代码(使用表达式树),不需要任何复杂的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类型。您可以用泛型类型T替换User,从而为任何类型的对象提供泛型规则编译器。此外,代码应该处理错误,比如未知的操作符名称。
请注意,即使在引入表达式树API之前,使用Reflection.Emit也可以动态生成代码。LambdaExpression.Compile()方法使用Reflection。在被子下发射(您可以使用ILSpy看到这一点)。