Cet extrait de code compile le Règlement rapide du code exécutable (à l'aide de l'Expression des arbres) et n'a pas besoin compliqué instructions de commutation:
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();
}
Vous pouvez alors écrire:
List<Rule> rules = new List<Rule> {
new Rule ("Age", "GreaterThan", "20"),
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));
}
Ici est la mise en œuvre de 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 == 15'
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);
}
}
Notez que j'ai utilisé "GreaterThan' au lieu de 'greater_than", etc. - c'est parce que "GreaterThan' est la .NET nom de l'opérateur, donc nous n'avons pas besoin supplémentaire de la cartographie.
Si vous avez vraiment besoin de noms personnalisés, vous pouvez construire un système très simple dictionnaire et traduire tous les opérateurs avant de compiler les règles:
var nameMap = new Dictionary<string, string> {
{ "greater_than", "GreaterThan" },
{ "hasAtLeastOne", "Contains" }
};
Notez que le code utilise le type de l'Utilisateur pour des raisons de simplicité. Vous pouvez remplacer l'Utilisateur avec un type générique T à avoir un générique Règle compilateur pour tous les types d'objets.
À noter également: la génération de code à la volée était possible, même avant de l'API des arbres d'Expression a été introduite, à l'aide de la Réflexion.En émettent. La méthode LambdaExpression.Compiler() utilise la Réflexion.Émettre sous les couvertures (vous pouvez voir ce à l'aide de ILSpy).