C#表达式树
前言
在使用 ef 开中我们经常使用 xx.where(p=>p.name="张三") 查询数据,之把能这样是因为 ef 框架会把这些c#代码转成sql语句, 其中主要用到的就是表达式树,今天就来学习一下表达式树。
认识表达式树
func<int,int,int> func = (a, b) => a + b; expression<func<int,int,int>> expression = (a, b) => a + b;
上面分别是 func 委托和表达式树,看上去很相似,左边只多了 expression<> 右边完全一样,其实还是有很大区别的,对于委托我们只能传递参数来调用,内部的代码在程序运行中是无从得知的,而表达式树在这点上相反,表达式树是一种数据结构,可以通过 c# 代码清晰的获取内部的细节。
表达式树的另一种写法
上面的例子中是使用 lambda 为表达式树赋值,其实还有另一种写法
parameterexpression parametera = expression.parameter(typeof(int),"a"); parameterexpression parameterb = expression.parameter(typeof(int),"b"); binaryexpression binaryexpression = expression.add(parametera, parameterb); expression<func<int, int, int>> expression = expression.lambda<func<int, int, int>>(binaryexpression,parametera,parameterb);
上面的例子对应于第一种写法,第一种写法是语法糖,其实编译器最终生成还是这种代码,可以通过反编译软件来验证。
解析表达式树
认识一下表达式树的主要部分
body:表达式主体,例子中是二元表达式,常用的还有
- constantexpression:常量表达式
- parameterexpression:参数表达式
- unaryexpression:一元运算符表达式
- binaryexpression:二元运算符表达式
- typebinaryexpression:is运算符表达式
- conditionalexpression:条件表达式
- memberexpression:访问字段或属性表达式
- methodcallexpression:调用成员函数表达式
- expression<tdelegate>:委托表达式
nodetype:节点类型,例子中是 lambda ,常用还有的+,-,*,/,>,=,<,&&,|| 等都有,不过并不是符号而是对应的英文,详情查看 expressiontype 枚举
parameters:表达式的参数,a 和 b
console.writeline(expression.body); console.writeline(expression.nodetype); console.writeline(expression.parameters[0]); console.writeline(expression.parameters[1]);
输出是
(a + b)
lambda
a
b
body 的类型是 expression,例子中的是二元表达式,所以要转换成 binaryexpression 类来查看信息
binaryexpression binaryexpression = (binaryexpression)expression.body; console.writeline(binaryexpression.left); console.writeline(binaryexpression.right); console.writeline(binaryexpression.nodetype);
输出是
a
b
add
刚才是一个简单表达式,再来看两个复杂点的,经过第一次解析后 left 和 right 就是第一种解析的表达式,可以把 left 和 right 再解析一次,最终完全解析,不管多复杂的表达式都可以像这样解析出来
上面的例子只是为了了解表达式树结构,用这种方法解析存在两个问题
一是 binaryexpression 这里固定了只能解析二元表达式,如果是其它表达式就会报错
二是不知道需要解析多少层才解析完
要解析表达式树要用 c# 里的 expressionvisitor 类,这个类就是专门解析表达式树的,它是一个抽象类,需要建个类继承它,使用过程如下,首先调用父类 visit 方法,在 visit 中会判断表达式的类型是一元(对应visitunary)、二元(对应visitbinary),常量(对应visitconstant)、参数(对应visitparameter)等表达式,然后就会进对应的解析方法中支解析,比如二元表达式的解析方法就是 visitbinary,然后我们重写 visitbinary
下面是使用 expressionvisitor 解析表达式树的例子,这么说并不完全对,解析代码是 expressionvisitor 已经写好的,我们做的只解析过程中加入一些自己的代码而已
首尾呼应
最后来实现一个简单的由表达式树生成sql语句的功能
class myvisitor : expressionvisitor { private string tablename; private stringbuilder sbsql = new stringbuilder(); public override expression visit(expression node) { return base.visit(node); } protected override expression visitbinary(binaryexpression node) { base.visit(node.left); sbsql.append(expressiontypetosql(node.nodetype)); base.visit(node.right); return node; } public string getsqlstring() { return "select * from "+tablename+" where "+sbsql.tostring(); } protected override expression visitconstant(constantexpression node) { if (node.type == typeof(int)) { sbsql.append( node.value); } else { sbsql.append("'"+node.value+"'"); } return base.visitconstant(node); } protected override expression visitparameter(parameterexpression node) { if (tablename == null) { tablename = "[" + node.type.name + "]"; } return base.visitparameter(node); } protected override expression visitmember(memberexpression node) { sbsql.append("[" + node.member.name + "]"); return base.visitmember(node); } public string expressiontypetosql(expressiontype expressiontype) { switch (expressiontype) { case expressiontype.add: return " + "; case expressiontype.and: case expressiontype.andalso: return " and "; case expressiontype.equal: return " = "; case expressiontype.notequal: return " != "; case expressiontype.greaterthan: return " > "; case expressiontype.greaterthanorequal: return " >= "; case expressiontype.lessthan: return " < "; case expressiontype.lessthanorequal: return " <= "; case expressiontype.multiply: return " * "; case expressiontype.or: case expressiontype.orelse: return " or "; default: return ""; } } }
expression<func<person, bool>> expression = p=>p.name=="张三"&&p.name!="李四"; myvisitor myvisitor = new myvisitor(); myvisitor.visit(expression); console.writeline(myvisitor.getsqlstring());
写得有点乱,忘见谅