技术开发 频道

C#中Specification模式的实现

  于是,我们如果要实现And和Or,做的第一件事情便是统一两个表达式树的参数,于是我们准备一个ExpressionVisitor:

internal class ParameterReplacer : ExpressionVisitor
{
    
public ParameterReplacer(ParameterExpression paramExpr)
    {
        
this.ParameterExpression = paramExpr;
    }

    
public ParameterExpression ParameterExpression { get; private set; }

    
public Expression Replace(Expression expr)
    {
        
return this.Visit(expr);
    }

    
protected override Expression VisitParameter(ParameterExpression p)
    {
        
return this.ParameterExpression;
    }
}

  ExpressionVisitor几乎是处理表达式树这种数据结构的不二法门,它可以用于求值,变形(其实是生成新的结构,因为表达式树是immutable的数据结构)等各种操作。例如,解决表达式树的缓存时用它来求树的散列值或读写前缀树,快速计算表达式时用它来提取表达式树的参数,并将不同的表达式树“标准化”为相同的结构。

  ExpressionVisitor基类的关键,就在于提供了遍历表达式树的标准方式,如果您直接继承这个类并调用Visit方法,那么最终返回的结果便是传入的Expression参数本身。但是,如果您覆盖的任意一个方法,返回了与传入时不同的对象,那么最终的结果就会是一个新的Expression对象。ExpressionVisitor类中的每个方法都负责一类表达式,也都都遵循了类似的原则:它们会递归地调用Visit方法,如果Visit返回新对象,那么它们也会构造新对象并返回。

  ParameterReplacer类的作用是将一个表达式树里的所有ParameterExpression替换成我们指定的新对象,因此只需覆盖VisitParameter方法就可以了。有了它之后,And和Or方法的实现轻而易举:

public static Expression<Func<T, bool>> And<T>(
    
this Expression<Func<T, bool>> one, Expression<Func<T, bool>> another)
{
    var candidateExpr
= Expression.Parameter(typeof(T), "candidate");
    var parameterReplacer
= new ParameterReplacer(candidateExpr);

    var left
= parameterReplacer.Replace(one.Body);
    var right
= parameterReplacer.Replace(another.Body);
    var body
= Expression.And(left, right);

    
return Expression.Lambda<Func<T, bool>>(body, candidateExpr);
}

public static Expression<Func<T, bool>> Or<T>(
    
this Expression<Func<T, bool>> one, Expression<Func<T, bool>> another)
{
    var candidateExpr
= Expression.Parameter(typeof(T), "candidate");
    var parameterReplacer
= new ParameterReplacer(candidateExpr);

    var left
= parameterReplacer.Replace(one.Body);
    var right
= parameterReplacer.Replace(another.Body);
    var body
= Expression.Or(left, right);

    
return Expression.Lambda<Func<T, bool>>(body, candidateExpr);
}

  于是,我们最终构造得到的Expression<Func<T, bool>>对象便可以传入一个LINQ Provider,最终得到查询结果:

Expression<Func<int, bool>> f = i => i % 2 == 0;
f
= f.Not().And(i => i % 3 == 0).Or(i => i % 4 == 0);

foreach (var i in new int[] { 1, 2, 3, 4, 5, 6 }.AsQueryable().Where(f))
{
    Console.WriteLine(i);
}

  输出的结果是3和4。

0
相关文章