c#:表达式树概念及应用场景(Expression)

环境:

  • window 10
  • vs2019 16.5.1
  • .netcore 3.1
  • .Net Reflector 10
  • ILSpy版本6.0.0.5559-preview2

参考:

  • 表达式树
  • Expression 核心操作符、表达式、操作方法
  • 整理:C#中Expression表达式的妙用
  • C# 表达式树讲解(一)
  • 微软文档(Expression 类)

目的:
探索什么是表达式树?
表达式能用来做什么?
表达式树在Entity Framework中起到了什么作用?

一、什么是表达式树

在c#中,我们可以定义一种树状的数据结构来描述c#中的代码,这种树状的数据结构就是表达式树,也称之为表达式(各种表达式之间是可以相互嵌套的)。比如说:(5-2)+(2+3)这个表达式,拆分成树状结构如下图:
c#:表达式树概念及应用场景(Expression)_第1张图片
当然,c#代码肯定比这个要复杂的多,比如:你的定义语句、循环、判断、属性访问等等。。。
在c#中,微软为每中运算类型的代码定义了不同的表达式类型,它们有共同的基类:Expression。

表达式树基类 Expression(抽象类)

它表示所有出现在c#中的代码的类型,主要包含两个属性:

  • NodeType:这个表达式的节点类型,这是一个枚举,c#中定义了85个节点类型
    c#:表达式树概念及应用场景(Expression)_第2张图片
  • Type:这个表达式的静态类型,也就是这个表达式的返回类型

二、常用的表达式类型有哪些?

我们直接看有哪些表达式类是继承自Expression的:
c#:表达式树概念及应用场景(Expression)_第3张图片
当然还有没有直接继承Expression的,比如:Expression(我们最常用的表达式树,继承自LambdaExpression),这里我就挑选几个表达式树简单描述一下:

  • 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就是委托类型的,继承自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();
 }

运行如下:
c#:表达式树概念及应用场景(Expression)_第4张图片

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
    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;
    }
}

整个代码输出如下:
c#:表达式树概念及应用场景(Expression)_第5张图片
从上面可以看到表达式树不仅可以组装简单的lambda表达式,还可以带语句块的复杂方法。

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();
}

毫无疑问,运行效果是一样的:
c#:表达式树概念及应用场景(Expression)_第6张图片
我们用.Net Reflector反编译看一下:
c#:表达式树概念及应用场景(Expression)_第7张图片
可以看到,这就是个语法糖。我们在entity framework中经常会用到这种语法糖,看下图:
在这里插入图片描述
c#:表达式树概念及应用场景(Expression)_第8张图片
在这里插入图片描述
从图中可以看到,entity framework中的参数传递用的都是表达式树(而不是我们熟悉的委托。。。),但一般我们穿进去的是lambda表达式,这里也是语法糖:编译器会在编译的时候将你写的委托组装成表达式树再传进入:
c#:表达式树概念及应用场景(Expression)_第9张图片
注意:
这个语法糖有一个限制:就是你的lambda代码不能有代码块(而这个特点正好被用在了entity framework上防止你任意的输入无限量代码)!
看下图:
c#:表达式树概念及应用场景(Expression)_第10张图片

四、表达式树能做什么?

上面讲到了,手动组装表达式树然后编译调用,现实中肯定不能这么用,如果是自己组装表达式再自己调用的话,为什么不干脆直接调用委托?
所以说,表达式树是给框架的作者用的。试想一下:当你调用框架方法的时候,你会传入一个简单的lambda表达式(lambda表达式还是很方便的),然后框架拿到你传入的表达式树(编译器编译时就已经将你的lambda表达式改装成了表达式树)并进行分析结构,然后做一些东西(对于entity framework来说就是分析表达式树 -> 生成适当sql语句->执行sql语句)。

未完待续。。。

你可能感兴趣的:(.netcore,表达式树,c#,Expression)