大家好,由于今天項(xiàng)目升級(jí),大家都在獲最新代碼,所以我又有時(shí)間在這里寫點(diǎn)東西,跟大家分享。
在上一篇的文章中我介紹了一個(gè)dll,使大家在debug的時(shí)候可以可視化的看到ExpressionTree的Body和Parameter。今天這篇文章主要講一個(gè)問題——如何利用一個(gè)已有的表達(dá)式樹的body來構(gòu)建一個(gè)新的表達(dá)式樹。 不多說廢話,現(xiàn)在開始進(jìn)入正題。
假設(shè)我們要寫一個(gè)像下面這樣的方法:
這個(gè)方法的用意很簡單,就是把傳入的兩個(gè)參數(shù)為string類型,返回類型為bool的方法做一個(gè)且的關(guān)系,構(gòu)建出一個(gè)新的以string為參數(shù),返回類型為bool的方法。
舉個(gè)例子,如果傳入的lambd0,lambd1為如下謂語表達(dá)式:
Expression<Func<string, bool>> lambda1 = item => item.Length < 4;
那么,我們希望ReBuildExpression這個(gè)方法返回的謂語表達(dá)式為item=>item.Length>2&&item.Length<4。
通過上一篇的介紹我們知道,對于一個(gè)表達(dá)式樹來說,我們可以把它分為2部分——body和parameter,邏輯在body,參數(shù)在parameter,那么很自然的,我們想到采用如下方式來實(shí)現(xiàn)這個(gè)方法:
代碼
{
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();
}
相信通過前幾篇的介紹大家已經(jīng)能大致看懂這段程序了,這段程序只是把傳入的兩個(gè)表達(dá)式樹做了一個(gè)且的關(guān)系,構(gòu)建出一個(gè)新的謂語表達(dá)式并傳出。
那么,這段程序的運(yùn)行結(jié)果如何呢?
大家如果調(diào)用這個(gè)方法,運(yùn)行程序就會(huì)發(fā)現(xiàn)程序會(huì)拋出一個(gè)異常:Lambda Parameter not in scope。
這個(gè)異常字面的意思是Lambda參數(shù)不在作用范圍內(nèi),為什么會(huì)有這個(gè)異常呢?
下面,請?jiān)试S我用一個(gè)例子來解釋這個(gè)問題。
我們將一個(gè)表達(dá)式樹比作一輛2輪自行車,那么body就是自行車骨架,parameter就是2個(gè)車輪。
好了,我們可以把上面代碼中的lambd0和lambd1看成2輛雙輪自行車。
我們在代碼中想把這2輛雙輪自行車拼接成一輛3人騎的4輪自行車,所以我們寫了以下代碼:
Expression left = lambd0.Body;
Expression right = lambd1.Body;
注意!這就是問題的關(guān)鍵所在,這里我們只是引用了這2個(gè)自行車的骨架,而不是復(fù)制!我們希望構(gòu)造出的4輪自行車沒有任何骨架,這2句只是說想引用已有的2個(gè)骨架,但問題就來了,已有的自行車骨架還連接著lambd0和lambd1的車輪,并不能被新的自行車所用,我們必須按照已有的骨架復(fù)制出一個(gè)一模一樣的骨架才能被我們的新的4輪自行車所用。
在這里截個(gè)lambda0的圖給大家看:

這是用上一篇的工具看到的,大家注意看紅色框框中的部分,Body中的memberExpression是記錄了parameter的信息的,這就是問題所在。
首先,我們要在項(xiàng)目中加入一個(gè)新的文件——ExpressionVisitor.cs,這個(gè)文件是上一篇的dll中的一個(gè)源文件./Files/FlyEdward/ExpressionVisitor.zip
大家可以從上面的鏈接中把它下載下來。我在這里粘貼這個(gè)類的聲明給大家看看
{
protected ExpressionVisitor()
{
}
protected virtual Expression Visit(Expression exp)
{
大家在這里可以看到這個(gè)類是個(gè)抽象類,然后Visit的訪問權(quán)限也是protected。
所以,我們必須再自己實(shí)現(xiàn)一個(gè)類,并且暴露出一個(gè)public的類來調(diào)用這個(gè)Visit方法。
這里解釋一下,ExpressionVisitor這個(gè)類的設(shè)計(jì)初衷是修改表達(dá)式樹而并非復(fù)制表達(dá)式樹,所以才把類設(shè)計(jì)成了abstract的,并且visit還是protected的,目的就是要用戶自己實(shí)現(xiàn)一個(gè)子類,定義修改的規(guī)則,下面的鏈接是一個(gè)例子,把一個(gè)表達(dá)式樹中的所有“且”邏輯修改成“或”邏輯。
http://msdn.microsoft.com/zh-cn/library/bb546136.aspx
好了,接著我們的項(xiàng)目說,我們自己實(shí)現(xiàn)一個(gè)子類來調(diào)用visit方法來訪問并復(fù)制表達(dá)式樹。
代碼
{
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;
}
}
大家注意,在這個(gè)類里我們新增了一個(gè)Parameter屬性,我們可以把新的Parameter賦給這個(gè)屬性,而不要它去訪問以前的“車輪”。
好了,接著,就可以實(shí)現(xiàn)之前的那個(gè)方法了:
代碼
{
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句只是表示這個(gè)表達(dá)式樹對應(yīng)的參數(shù)類型為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();
}
}
這段程序的運(yùn)行結(jié)果就是輸出長度大于2,小于4的"Cai"。
今天就跟大家分享到這里,下一篇中我將結(jié)合自己開發(fā)中遇到的問題,跟大家舉例講解一些表達(dá)式樹的應(yīng)用場景和今天講的ExpressionVisitor的應(yīng)用場景。希望大家能繼續(xù)關(guān)注。

浙公網(wǎng)安備 33010602011771號(hào)