上一篇文章中我們利用C#語言的特性實現了一種輕量級的Specification模式,它的關鍵在於拋棄了具體的Specification類型,而是使用一個委托對象代替唯一關鍵的IsSatisfIEdBy方法邏輯。據我們分析,其優勢之一在於使用簡單,其劣勢之一在於無法靜態表示。但是它們還都是在處理“業務邏輯”,如果涉及到一個用於LINQ查詢或其他地方的表達式樹,則問題就不那麼簡單了——但也沒有我們想象的那麼復雜。
好,那麼我們就把場景假想至LINQ上。LINQ與普通業務邏輯不同的地方在於,它不是用一個IsSatisfIEdBy方法或一個委托對象用來表示判斷邏輯,而是需要構造一個表達式樹,一種數據結構。如果您還不清楚表達式樹是什麼,那麼可以看一下腦袋的寫的上手指南。這是.NET 3.5帶來的重要概念,在4.0中又得到了重要發展,如果您要在.Net方面前進,這是一條必經之路。
And、Or和Not之間,最容易處理的便是Not方法,於是我們從這個地方下手,直接來看它的實現:
public static class SpecExprExtensions
{
public static Expression<Func<T, bool>> Not<T>(this Expression<Func<T, bool>> one)
{
var candidateExpr = one.Parameters[0];
var body = Expression.Not(one.Body);
return Expression.Lambda<Func<T, bool>>(body, candidateExpr);
}
}
一個Expression<TDelegate>對象中主要有兩部分內容,一是參數,二是表達式體(Body)。對於Not方法來說,我們只要獲取它的參數表達式,再將它的Body外包一個Not表達式,便可以此構造一個新的表達式了。這部分邏輯非常簡單,看了腦袋的文章,了解了表達式樹的基本結構就能理解這裡的含義。那麼試驗一下:
Expression<Func<int, bool>> f = i => i % 2 == 0;
f = f.Not();
foreach (var i in new int[] { 1, 2, 3, 4, 5, 6 }.AsQueryable().Where(f))
{
Console.WriteLine(i);
}
打印出來的自然是所有的奇數,即1、3、5。
而And和Or的處理上會有所麻煩,我們不能這樣像Not一樣簡單處理:
public static Expression<Func<T, bool>> And<T>(
this Expression<Func<T, bool>> one, Expression<Func<T, bool>> another)
{
var candidateExpr = one.Parameters[0];
var body = Expression.And(one.Body, another.Body);
return Expression.Lambda<Func<T, bool>>(body, candidateExpr);
}
這麼做雖然能夠編譯通過,但是在執行時便會出錯。原因在於one和another兩個表達式雖然都是同樣的形式(Expression<Func<T, bool>>),但是它們的“參數”不是同一個對象。也就是說,one.Body和another.Body並沒有公用一個 ParameterExpression實例,於是我們無論采用哪個表達式的參數,在Expression.Lambda方法調用的時候,都會告訴您新的 body中的某個參數對象並沒有出現在參數列表中。
於是,我們如果要實現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;
}
}