在继续探讨如何生成Expression前先对委托和表达式关系做下简单介绍,方便更好的理解后面的内容。
.net提供的Expression涵盖的表达式范围很大,从一般的含操作符表达式到带语句的表达式都支持,比如像if, foreach, try ... catch这些都可以成为表达式的一部分,但是带语句体的表达式很难被转换成SQL语句,而expression很大程度上是为LINQ服务的,所以VS编译的时候是不支持静态编译带语句体的表达式的,所有带语句体的表达式都会被直接编译成执行代码也就是做成一个委托,这样做的好处一来是简化编写DLINQ provider的工作,二来也是能消除歧义。比如
void F(Func<int> d)
void F(Expression<Func<int>> d)
调用 F(()=> { return 0; }) 时,因为编译器默认语句体不是表达式,所以直接会找到F(Func<int>d)这个方法。当然如果是 F(()=>0) 这样调用的话编译器还是会搞不清直接报错,.net解决方法是把linq to object 和 linq to db区分开:
IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate);
IQueryable<TSource> Where<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate);
当IEnumeralbe来调用就是用委托,IQueryable来调用就是表达式。这样一来,从编写角度看就是IEnumeralbe可以接受带语句体和不带语句体的表达式,而IQueryable就是只接受不带语句体的表达式,也就是说在用linq查询数据库的时候,当你的表达式带语句体的时候就不会产生sql语句而是在做linq to object。
这个特性是vs的一个硬性规定,为了简单起见接下来生成的表达式也是按照这个规定,当然通过增加解析处理的代码也可以把形如 ()=>{ ... } 这种语句体形式转成真正的表达式而不是委托,不过我测试下来就算你得到了一个 ()=>{ return true; } 这么个表达式,也不能把它用到IQueryable的Where方法里,因为provider不支持这种形式的表达式,所以这样的转换意义不是很大,能想到这样处理有用的地方就是:利用表达式能被运行时编译执行这个特点把它当做编译型脚本来用。
接下来开始讲表达式转换,首先讲最简单的ConstantExpression,先来看一下语法树的图
非常简单的一个lambda ()=>true 它被之前修改过的Grammar解析成右边这棵树,primary_expression在解析的过程中会一直接触到。上一篇说了我们的Grammar Root被设置为expression,expression的bnf为
- expression.Rule = conditional_expression
- | bin_op_expression
- | typecast_expression
- | primary_expression
- | query_expression;
这表示expression 可以被分解为 条件表达式(?:) 二元操作表达式(+-*/...) 类型转换表达式 primay_expression 以及代表linq的查询表达式,就是说我们能解析的东西必须是这5种类型之一,否则就无法解析。
再看primay_expression
- primary_expression.Rule =
- literal
- | unary_operator + primary_expression
- | parenthesized_expression
- | member_access
- | pre_incr_decr_expression
- | post_incr_decr_expression
- | object_creation_expression
- | anonymous_type_creation_expression
- | typeof_expression
- | checked_expression
- | unchecked_expression
- | default_value_expression
- | anonymous_method_expression
- | lambda_expression;
东西非常多,其中lambda_expression 表示的就是形如(int a) => ... 的lambda表达式, 而literal就是需要被转换成ConstantExpression的节点。
在上篇提到的ProcessExpression这个方法里,处理literal的是
- case "literal":
- return ProcessConstantExpression(expNode);
ProcessConstantExpression的实现
- private Expression ProcessConstantExpression(ParseTreeNode expNode)
- {
- var constant = expNode.FirstChild;
- return Expression.Constant(constant.GetObject());
- }
由于是常量,转换非常简单,取到literal节点的第一个子节点,也就是true这个节点,然后直接把里面存的内容返回成ConstantExpression就行了,是不是很简单?以后要讲的表达式虽然复杂,但是大致上的思路都是一致的。