大家好,由於今天項目升級,大家都在獲最新代碼,所以我又有時間在這里寫點東西,跟大家分享。
在上一篇的文章中我介紹了一個dll,使大家在debug的時候可以可視化的看到ExpressionTree的Body和Parameter。今天這篇文章主要講一個問題——如何利用一個已有的表達式樹的body來構建一個新的表達式樹。 不多說廢話,現在開始進入正題。
假設我們要寫一個像下面這樣的方法:
這個方法的用意很簡單,就是把傳入的兩個參數為string類型,返回類型為bool的方法做一個且的關系,構建出一個新的以string為參數,返回類型為bool的方法。
舉個例子,如果傳入的lambd0,lambd1為如下謂語表達式:
Expression<Func<string, bool>> lambda1 = item => item.Length < 4;
那么,我們希望ReBuildExpression這個方法返回的謂語表達式為item=>item.Length>2&&item.Length<4。
通過上一篇的介紹我們知道,對於一個表達式樹來說,我們可以把它分為2部分——body和parameter,邏輯在body,參數在parameter,那么很自然的,我們想到采用如下方式來實現這個方法:

{
parameter = Expression.Parameter(typeof(string), "name");
Expression left = lambd0.Body;
Expression right = lambd1.Body;
BinaryExpression expression = Expression.AndAlso(left, right);
Expression<Func<string, bool>> lambda = Expression.Lambda<Func<string, bool>>(expression, parameter);
return lambda.Compile();
}
相信通過前幾篇的介紹大家已經能大致看懂這段程序了,這段程序只是把傳入的兩個表達式樹做了一個且的關系,構建出一個新的謂語表達式並傳出。
那么,這段程序的運行結果如何呢?
大家如果調用這個方法,運行程序就會發現程序會拋出一個異常:Lambda Parameter not in scope。
這個異常字面的意思是Lambda參數不在作用范圍內,為什么會有這個異常呢?
下面,請允許我用一個例子來解釋這個問題。
我們將一個表達式樹比作一輛2輪自行車,那么body就是自行車骨架,parameter就是2個車輪。
好了,我們可以把上面代碼中的lambd0和lambd1看成2輛雙輪自行車。
我們在代碼中想把這2輛雙輪自行車拼接成一輛3人騎的4輪自行車,所以我們寫了以下代碼:
Expression left = lambd0.Body;
Expression right = lambd1.Body;
注意!這就是問題的關鍵所在,這里我們只是引用了這2個自行車的骨架,而不是復制!我們希望構造出的4輪自行車沒有任何骨架,這2句只是說想引用已有的2個骨架,但問題就來了,已有的自行車骨架還連接着lambd0和lambd1的車輪,並不能被新的自行車所用,我們必須按照已有的骨架復制出一個一模一樣的骨架才能被我們的新的4輪自行車所用。
在這里截個lambda0的圖給大家看:
這是用上一篇的工具看到的,大家注意看紅色框框中的部分,Body中的memberExpression是記錄了parameter的信息的,這就是問題所在。
所以就有了下面的解決方案。
首先,我們要在項目中加入一個新的文件——ExpressionVisitor.cs,這個文件是上一篇的dll中的一個源文件./Files/FlyEdward/ExpressionVisitor.zip
大家可以從上面的鏈接中把它下載下來。我在這里粘貼這個類的聲明給大家看看
{
protected ExpressionVisitor()
{
}
protected virtual Expression Visit(Expression exp)
{
大家在這里可以看到這個類是個抽象類,然后Visit的訪問權限也是protected。
所以,我們必須再自己實現一個類,並且暴露出一個public的類來調用這個Visit方法。
這里解釋一下,ExpressionVisitor這個類的設計初衷是修改表達式樹而並非復制表達式樹,所以才把類設計成了abstract的,並且visit還是protected的,目的就是要用戶自己實現一個子類,定義修改的規則,下面的鏈接是一個例子,把一個表達式樹中的所有“且”邏輯修改成“或”邏輯。
http://msdn.microsoft.com/zh-cn/library/bb546136.aspx
好了,接着我們的項目說,我們自己實現一個子類來調用visit方法來訪問並復制表達式樹。

{
public ParameterExpression Parameter
{
get;
set;
}
public System.Linq.Expressions.Expression Modify(System.Linq.Expressions.Expression exp)
{
return this.Visit(exp);
}
protected override Expression VisitParameter(ParameterExpression p)
{
return Parameter;
}
}
大家注意,在這個類里我們新增了一個Parameter屬性,我們可以把新的Parameter賦給這個屬性,而不要它去訪問以前的“車輪”。
好了,接着,就可以實現之前的那個方法了:

{
ExpressionVisitorMy visitor = new ExpressionVisitorMy();
ParameterExpression parameter = Expression.Parameter(typeof(string), "name");
visitor.Parameter = parameter;
Expression left = visitor.Modify(lambd0.Body);
Expression right = visitor.Modify(lambd1.Body);
BinaryExpression expression = Expression.AndAlso(left, right);
Expression<Func<string, bool>> lambda = Expression.Lambda<Func<string, bool>>(expression, parameter);
return lambda.Compile();
}
大家注意:
ParameterExpression parameter = Expression.Parameter(typeof(string), "name");
visitor.Parameter = parameter;
這2句只是表示這個表達式樹對應的參數類型為string,形參的名字是name。
好,下面貼出Program中的完整代碼:

{
static void Main(string[] args)
{
List<string> names = new List<string> { "Cai", "Edward", "Beauty" };
Expression<Func<string, bool>> lambda0 = item => item.Length > 2;
Expression<Func<string, bool>> lambda1 = item => item.Length < 4;
Expression<Func<string, bool>> lambda2 = name => name.Length > 2 && name.Length < 3;
Program program = new Program();
Func<string, bool> method = program.ReBuildExpression(lambda0, lambda1);
var query = names.Where(method);
foreach (string n in query)
{
Console.WriteLine(n);
}
Console.Read();
}
public Func<string, bool> ReBuildExpression(Expression<Func<string, bool>> lambd0, Expression<Func<string, bool>> lambd1)
{
ExpressionVisitorMy visitor = new ExpressionVisitorMy();
ParameterExpression parameter = Expression.Parameter(typeof(string), "name");
visitor.Parameter = parameter;
Expression left = visitor.Modify(lambd0.Body);
Expression right = visitor.Modify(lambd1.Body);
BinaryExpression expression = Expression.AndAlso(left, right);
Expression<Func<string, bool>> lambda = Expression.Lambda<Func<string, bool>>(expression, parameter);
return lambda.Compile();
}
}
這段程序的運行結果就是輸出長度大於2,小於4的"Cai"。
今天就跟大家分享到這里,下一篇中我將結合自己開發中遇到的問題,跟大家舉例講解一些表達式樹的應用場景和今天講的ExpressionVisitor的應用場景。希望大家能繼續關注。