大家好,由于今天项目升级,大家都在获最新代码,所以我又有时间在这里写点东西,跟大家分享。
在上一篇的文章中我介绍了一个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的应用场景。希望大家能继续关注。