环境:
- window 10
- vs2019 16.5.1
- .netcore 3.1
- .Net Reflector 10
- ILSpy版本6.0.0.5559-preview2
参考:
目的:
探索什么是表达式树?
表达式能用来做什么?
表达式树在Entity Framework中起到了什么作用?
一、什么是表达式树
在c#中,我们可以定义一种树状的数据结构来描述c#中的代码,这种树状的数据结构就是表达式树,也称之为表达式(各种表达式之间是可以相互嵌套的)。比如说:(5-2)+(2+3)
这个表达式,拆分成树状结构如下图:
当然,c#代码肯定比这个要复杂的多,比如:你的定义语句、循环、判断、属性访问等等。。。
在c#中,微软为每种运算类型的代码定义了不同的表达式类型,它们有共同的基类:Expression。
表达式树基类 Expression
(抽象类)
说明:它表示所有出现在c#中的代码的类型,主要包含两个属性:NodeType和Type。
它的定义如下图:
说下Expression
的两个属性:
- NodeType:这个表达式的节点类型,这是一个枚举,c#中定义了85个节点类型
- Type:这个表达式的静态类型,也就是这个表达式的返回类型
二、常用的表达式类型有哪些?
我们直接看有哪些表达式类是继承自Expression
的:
当然还有没有直接继承Expression
的,比如:Expression<T>
(我们最常用的表达式树)的继承关系为:Expression<T>
=>LambdaExpression
=>Expression
=>Object
,这里我就挑选几个表达式树简单描述一下:
- BinaryExpression:二元运算表达式
1). Right:二元运算符的右侧表达式(Expression)
2). Left:二元运算符的左侧表达式(Expression) - ConstantExpression:常量表达式
1). Value:常量值,Object - ConditionalExpression:条件表达式
1). IfFalse:为False时的表达式(Expression)
2). IfTrue:为True时的表达式(Expression)
3). Test:判断表达式 - ParameterExpression:参数表达式
1). IsByRef:参数是否传引用
2). Name:参数名称 - LambdaExpression:lambda表达式
1).Body:内容表达式(Expression)
2).Parameters:参数表达式集合(ReadOnlyCollection)
3).ReturnType:返回的类型
4).Compile():方法,编译生成委托 - Expression<T>:带有泛型的表达式,一般这个
T
就是委托类型的,继承自LabelExpression
三、怎么创建表达式树
3.1 纯手工组装
3.1.1 将简单lambda表达式:(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();
}
运行如下:
3.1.2 将复杂的方法定义用表达式树的形式表示出来
原方法定义:
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<standard
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表达式,还可以带语句块的复杂方法。
3.1.3 应用示例
我们可以利用表达式树完成AutoMapper类似的功能,下面的示例展示如何将Person
动态转换成PersonDto
,这在批量转换时性能是很高的:
// 模型定义:(PersonDto除了和Person的类名不同,其他的一样)
public enum EnumSex
{
Male, FeMale
}
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public string Addr { get; set; }
public string IdentityNo { get; set; }
public EnumSex Sex { get; set; }
public DateTime Birth { get; set; }
public string Description { get; set; }
public int Age { get; set; }
public string Nick { get; set; }
public string QQ { get; set; }
public string Email { get; set; }
public string Phone { get; set; }
public string WeiXin { get; set; }
public string Picture { get; set; }
public string Intro { get; set; }
public double Score { get; set; }
public List<string> Books { get; set; }
}
//获取委托
private static Func<TFrom, TDest> getMapperFunc<TFrom, TDest>()
{
var para = Expression.Parameter(typeof(TFrom), "src");
//var res=new TDest();
var variable = Expression.Variable(typeof(TDest), "res");
var newExp = Expression.New(typeof(TDest).GetConstructor(new Type[0]));
var assign = Expression.Assign(variable, newExp);
// 属性赋值
var assignProps = new List<BinaryExpression>();
var props = typeof(TFrom).GetProperties();
var destProps = typeof(TDest).GetProperties();
foreach (var prop in destProps)
{
if (prop.CanWrite)
{
var srcProp = props.FirstOrDefault(i => i.Name == prop.Name);
if (srcProp.Name == prop.Name)
{
//属性
var assignProp = Expression.Assign(Expression.Property(variable, prop.Name), Expression.MakeMemberAccess(para, srcProp));
assignProps.Add(assignProp);
}
}
}
//return res;
var labelTarget = Expression.Label(typeof(TDest));
var labelExpression = Expression.Label(labelTarget, variable);
var gotoExpression = Expression.Return(labelTarget, variable, typeof(TDest));
var expressions = new List<Expression>();
expressions.Add(assign);
expressions.AddRange(assignProps);
expressions.Add(gotoExpression);
expressions.Add(labelExpression);
//组装表达式块,编译生成委托
var block = Expression.Block(typeof(TDest), new ParameterExpression[] { variable }, expressions);
var mapperFunc = Expression.Lambda<Func<TFrom, TDest>>(block, para).Compile();
return mapperFunc;
}
public static void Test()
{
//定义tmpList集合,里面有1万个Person实体
var person = new Person(){Id=1};
PersonDto dto = null;
var func = getMapperFunc<Person, PersonDto>();
var count = 50;
st4 = new Stopwatch();
st4.Start();
for (var i = 0; i < count; i++)
{
var st2 = new Stopwatch();
st2.Start();
for (var j = 0; j < 10000; j++)
{
dto = func(person);
}
st2.Stop();
Console.WriteLine($"第{(i+1)}次: {st2.ElapsedMilliseconds}毫秒");
}
st4.Stop();
Console.WriteLine($"表达式树 总耗时: {st4.ElapsedMilliseconds}毫秒 平均: {st4.ElapsedMilliseconds / (count + 0.0)}毫秒");
}
3.2 使用编译器的语法糖
同样是上面的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语句)。
下一篇:《c#:深入理解表达式树》