环境:
参考:
目的:
探索什么是表达式树?
表达式能用来做什么?
表达式树在Entity Framework中起到了什么作用?
在c#中,我们可以定义一种树状的数据结构来描述c#中的代码,这种树状的数据结构就是表达式树,也称之为表达式(各种表达式之间是可以相互嵌套的)。比如说:(5-2)+(2+3)
这个表达式,拆分成树状结构如下图:
当然,c#代码肯定比这个要复杂的多,比如:你的定义语句、循环、判断、属性访问等等。。。
在c#中,微软为每中运算类型的代码定义了不同的表达式类型,它们有共同的基类:Expression。
Expression
(抽象类)它表示所有出现在c#中的代码的类型,主要包含两个属性:
我们直接看有哪些表达式类是继承自Expression
的:
当然还有没有直接继承Expression
的,比如:Expression
(我们最常用的表达式树,继承自LambdaExpression),这里我就挑选几个表达式树简单描述一下:
T
就是委托类型的,继承自LabelExpression(x,y)=>x+y
用表达式树的形式表示出来static void Main(string[] args)
{
//组装表达式树
ParameterExpression para1 = Expression.Parameter(typeof(int), "x");
ParameterExpression para2 = Expression.Parameter(typeof(int), "y");
var paras = new ParameterExpression[] {
para1, para2 };
BinaryExpression body = Expression.Add(para1, para2);
var expression = Expression.Lambda(body, paras);
//编译表达式树
var func = expression.Compile() as Func<int, int, int>;
//调用执行
var res = func(1, 2);
Console.WriteLine($"表达式树的执行结果:(1,2)=>{res}");
Console.WriteLine("Hello World!");
Console.ReadLine();
}
原方法定义:
public static string GetScoreDesc(string name, int score, int standard)
{
string desc = "";
if (score > standard)
{
desc = "已及格";
}
else if (score == standard)
{
desc = "刚及格";
}
else
{
desc = "未及格";
}
return name + desc;
}
用表达式树表示如下:
private static void Test()
{
//定义三个参数: string name,ing score,int standard
ParameterExpression paraName = Expression.Parameter(typeof(string), "name");
ParameterExpression paraScore = Expression.Parameter(typeof(int), "score");
ParameterExpression paraStandard = Expression.Parameter(typeof(int), "standard");
//定义一个局部变量:string desc;
ParameterExpression defineDesc = Expression.Parameter(typeof(string), "desc");
//给局部变量赋值:desc="";
BinaryExpression assignDesc = Expression.Assign(defineDesc, Expression.Constant(""));
//准备赋值语句:desc="已及格"
BinaryExpression assignDescYijige = Expression.Assign(defineDesc, Expression.Constant("已及格"));
//准备赋值语句:desc="刚及格"
BinaryExpression assignDescGangjige = Expression.Assign(defineDesc, Expression.Constant("刚及格"));
//准备赋值语句:desc="未及格"
BinaryExpression assignDescWeijige = Expression.Assign(defineDesc, Expression.Constant("未及格"));
//准备代码:score>standard
BinaryExpression greaterThan = Expression.MakeBinary(ExpressionType.GreaterThan, paraScore, paraStandard);
//准备代码:score==standard
BinaryExpression equalThan = Expression.MakeBinary(ExpressionType.Equal, paraScore, paraStandard);
//准备代码:score
BinaryExpression lessThan = Expression.MakeBinary(ExpressionType.LessThan, paraScore, paraStandard);
//组装判断逻辑块:if(score>standard){desc="已及格"}else{if(score==standard){desc="刚及格"}else{desc="未及格"}}
ConditionalExpression conditional = Expression.Condition(greaterThan, assignDescYijige, Expression.Condition(equalThan, assignDescGangjige, assignDescWeijige));
//准备代码:name+desc (注意:methodof运算符是用c#自定义的,代码在后面)
BinaryExpression addAssign = Expression.Add(paraName, defineDesc, (methodof<Func<string, string, string>>)(String.Concat));
//定义标记:这个标记用于方法返回值
LabelTarget labelTarget = Expression.Label(typeof(string));
//定义返回代码段:默认返回 desc
LabelExpression labelExpression = Expression.Label(labelTarget, defineDesc);
//定义return语句(这里是goto语句):goto labelTarget
GotoExpression gotoExpression = Expression.Return(labelTarget, addAssign, typeof(string));
//组装这个方法的方法体(指定局部变量)
BlockExpression block = Expression.Block(typeof(string), new ParameterExpression[] {
defineDesc }, assignDesc, conditional, gotoExpression, labelExpression);
//完成组装这个方法(指定参数)
LambdaExpression expression = Expression.Lambda<Func<string, int, int, string>>(block, new ParameterExpression[] {
paraName, paraScore, paraStandard });
//编译测试...
var delega = expression.Compile();
var func = delega as Func<string, int, int, string>;
var res = func("xiaoming", 5, 6);
Console.WriteLine($"func(\"xiaoming\", 5, 6)=>{func("xiaoming", 5, 6)}");
Console.WriteLine($"func(\"xiaoming\", 2, 6)=>{func("xiaoming", 2, 6)}");
Console.WriteLine($"func(\"xiaoming\", 6, 6)=>{func("xiaoming", 6, 6)}");
Console.WriteLine($"func(\"xiaoming\", 8, 6)=>{func("xiaoming", 8, 6)}");
//尝试打印出表达式树的字符串形式
Console.WriteLine("整体输出...");
Console.WriteLine(expression.ToString());
Console.WriteLine("--------------------------start");
Console.WriteLine(assignDesc.ToString());
Console.WriteLine(conditional);
Console.WriteLine(gotoExpression);
Console.WriteLine(labelExpression);
Console.WriteLine("---------------------------end");
}
上面用到的methodof运算符定义如下:
public class methodof<T>
{
private MethodInfo method;
public methodof(T func)
{
Delegate del = (Delegate)(object)func;
this.method = del.Method;
}
public static implicit operator methodof<T>(T methodof)
{
return new methodof<T>(methodof);
}
public static implicit operator MethodInfo(methodof<T> methodof)
{
return methodof.method;
}
}
整个代码输出如下:
从上面可以看到表达式树不仅可以组装简单的lambda表达式,还可以带语句块的复杂方法。
同样是上面的lambda表达式`:(x,y)=>x+y
,代码如下:
static void Main(string[] args)
{
//组装表达式树
Expression<Func<int, int, int>> expression = (x, y) => x + y;
//编译并调用
var res = expression.Compile()(1, 2);
Console.WriteLine($"表达式树的执行结果:(1,2)=>{res}");
Console.WriteLine("Hello World!");
Console.ReadLine();
}
毫无疑问,运行效果是一样的:
我们用.Net Reflector反编译看一下:
可以看到,这就是个语法糖。我们在entity framework中经常会用到这种语法糖,看下图:
从图中可以看到,entity framework中的参数传递用的都是表达式树(而不是我们熟悉的委托。。。),但一般我们穿进去的是lambda表达式,这里也是语法糖:编译器会在编译的时候将你写的委托组装成表达式树再传进入:
注意:
这个语法糖有一个限制:就是你的lambda代码不能有代码块(而这个特点正好被用在了entity framework上防止你任意的输入无限量代码)!
看下图:
上面讲到了,手动组装表达式树然后编译调用,现实中肯定不能这么用,如果是自己组装表达式再自己调用的话,为什么不干脆直接调用委托?
所以说,表达式树是给框架的作者用的。试想一下:当你调用框架方法的时候,你会传入一个简单的lambda表达式(lambda表达式还是很方便的),然后框架拿到你传入的表达式树(编译器编译时就已经将你的lambda表达式改装成了表达式树)并进行分析结构,然后做一些东西(对于entity framework来说就是分析表达式树 -> 生成适当sql语句->执行sql语句)。
未完待续。。。