一、前言

在我们日常开发中Lamba 表达式经常会使用,如List.Where(n=>Name="abc") 使用起来非常的方便,代码也很简洁,总之一个字就是“爽”。在之前我们总是用硬编码的方式去实现一些底层方法,比如我要查询用户“abc”是否存在,老的实现方式基本为:GetUser(string name) ,可能下次我需要按用户登录账号来查询,于是又得写一个方法:GetUserByLogin(string loginCode),我们认真想一下,如果能实现类似于集合查询那样只要写lambda 就能搞定,List.Where(n=>Name="abc"),List.Where(n=>LoginCode=="小A"),有了这样的想法,那我们也去了解一下lambda 表达式树的背后吧。

二、初识

表达式树关键字“Expressions”,我们在官方文档里面可以看到很多介绍,具体信息请查看微软官方文档库;官方文档里面的信息量比较大,有几十个对象的介绍:

这里我不建议大家从头到尾的看一遍,大致浏览就好了,因为信息量太多了。首先我们新建一个控制台程序,框架版本选FX4.0以上或者Core 都行,引入命名空间:

using System.Linq.Expressions; 接下来实现一个简单的功能,解析表达式: n=>n.Name="abc" 我们想要的结果是 Name="abc",有了这个目标我们就知道该干嘛了。

定义函数:private static string GetLambdaStr<T>(Expression<Func<T, bool>> expression),该函数定义了一个表达式树参数,Func<in T,out bool>是范型委托,该委托表示接收一个T类型参数,返回一个bool值。具体代码:

        private static string GetLambdaStr<T>(Expression<Func<T, bool>> expression)
{
//解析表达式
//return new Analysis().AnalysisExpression(expression);
return null;
}

接下来建立一个用户对象:

        public class User
{
public int ID { get; set; }
public string Name { get; set; }
public int Age { get; set; }
public bool States { get; set; }
}

再建立好测试代码:

        //Expression<Func<T, bool>> lambda = n => n.Name == "abc";
Console.WriteLine("lambda : n => n.Name == \"abc\" ");
var a = GetLambdaStr<User>(n => n.Name == "abc");
Console.WriteLine("result:" + a);
Console.Write(Environment.NewLine);
Console.ReadKey();

先不管那么多,我们调试进去看看表达式长啥样:

这样看比较清真,就是一个lambda表达式,我们展开看看对象明细:

看到这里我们是不是能想起点什么了,这其实就是一颗二叉树,显示的层次为根节点,左子节点,右子节点,依次循环。有了这个认知,我们立马能想到可以使用递归来遍历节点了。

于是我来了解表达式对象“Expression”有哪些属性和方法:

看到这里有点困惑了,刚刚我们明明看到有Left、Right 属性,但这里却没有,感觉好坑呀。没有左右节点我们根本不知道怎么去递归查找子节点呢。于是又去看官方介绍文档了,然后再仔细看了LambdaExpression 对象,这个是抽象类,有抽象必定有相关的实现或者提供对外属性,仔细一找,刚好找到BinaryExpression对象,有Left、Right属性同时继承了Expression对象,也提供了LambdaExpression 属性,这个就是我们要找的对象了,可以说是峰回路转了:

顺着这个思路,我又找到了属性成员和属性值对象MemberExpression、ConstantExpression,我们来实现关键代码

        private static string GetLambdaStr<T>(Expression<Func<T, bool>> expression)
        {
            //解析表达式
            var body = (BinaryExpression)expression.Body;
            var r = (ConstantExpression)body.Right;
            var l = (MemberExpression)body.Left;
            var value = r.Type.IsValueType ? r.Value : $"'{r.Value}'";
            return $"{ l.Member.Name} {Operand(body.NodeType)} {value} ";
            // return new Analysis().AnalysisExpression(expression);
        }

Operand 是操作类型转换,代码如下:

        //操作符转换
private string Operand(ExpressionType type)
{
string operand = string.Empty;
switch (type)
{
case ExpressionType.AndAlso:
operand = "AND";
break;
case ExpressionType.OrElse:
operand = "OR";
break;
case ExpressionType.Equal:
operand = "=";
break;
case ExpressionType.LessThan:
operand = "<";
break;
case ExpressionType.LessThanOrEqual:
operand = "<=";
break;
case ExpressionType.GreaterThan:
operand = ">";
break;
case ExpressionType.GreaterThanOrEqual:
operand = ">";
break;
}
return operand;
}

有了上面的代码我们已经完成功能了,运行结果如下:

三、进阶

日常开发中我们遇到的查询条件可能会更加复杂,于是我又写了几个复杂得表达式:

            //Expression<Func<T, bool>> lambda = n => n.states;
Console.WriteLine("analysis: n => n.states ");
var b = GetLambdaStr<User>(n => n.States);
Console.WriteLine("result:" + b);
Console.ReadKey(); //Expression<Func<T, bool>> lambda = n => n.Name == "abc" && n.Age > 30 || n.ID == 4;
Console.WriteLine("lambda: n => n.Name == \"abc\" && n.Age > 30 || n.ID == 4");
var c = GetLambdaStr<User>(n => n.Name == "abc" && (n.Age > || n.ID == ) && n.ID > 1
&& (n.ID > || n.Name == ""));
Console.WriteLine("result:" + c);
Console.Write(Environment.NewLine);
Console.ReadKey();

经过我的一番探索和调试,终于完成了解析:

建议手动去敲一遍代码,并调试,这中间我遇到了一些坑,比如使用了OR条件时需要增加括号,这个括号老是没放对位置。

最后贴出全部代码:

1、控制台代码

            //Expression<Func<T, bool>> lambda = n => n.Name == "abc";
Console.WriteLine("lambda : n => n.Name == \"abc\" ");
var a = GetLambdaStr<User>(n => n.Name == "abc");
Console.WriteLine("result:" + a);
Console.Write(Environment.NewLine);
Console.ReadKey(); //Expression<Func<T, bool>> lambda = n => n.states;
Console.WriteLine("analysis: n => n.states ");
var b = GetLambdaStr<User>(n => n.States);
Console.WriteLine("result:" + b);
Console.ReadKey(); //Expression<Func<T, bool>> lambda = n => n.Name == "abc" && n.Age > 30 || n.ID == 4;
Console.WriteLine("lambda: n => n.Name == \"abc\" && n.Age > 30 || n.ID == 4");
var c = GetLambdaStr<User>(n => n.Name == "abc" && (n.Age > || n.ID == ) && n.ID > && (n.ID > || n.Name == ""));
Console.WriteLine("result:" + c);
Console.Write(Environment.NewLine);
Console.ReadKey();

2、解析函数

        private static string GetLambdaStr<T>(Expression<Func<T, bool>> expression)
{
//解析表达式
return new Analysis().AnalysisExpression(expression);
}

3、用户对象代码上面已经有了就不重复发了

4、解析对象代码

    public class Analysis
{
private StringBuilder builder = new StringBuilder();
public string AnalysisExpression<TDelegate>(Expression<TDelegate> expression)
{
if (expression.Body is MemberExpression)
{
var m = (MemberExpression)expression.Body;
var value = Convert.ToInt32(!expression.Body.ToString().Contains("!"));
builder.Append($" ({m.Member.Name}={value}) ");
return builder.ToString();
}
var body = (BinaryExpression)expression.Body;
if (body.NodeType == ExpressionType.AndAlso || body.NodeType == ExpressionType.OrElse)
{
AnalysisExpressionChild((BinaryExpression)body.Left, body.NodeType);
AnalysisExpressionChild((BinaryExpression)body.Right, body.NodeType);
}
else
{
var r = (ConstantExpression)body.Right;
var l = (MemberExpression)body.Left;
var value = r.Type.IsValueType ? r.Value : $"'{r.Value}'";
builder.Append($" { l.Member.Name} {Operand(body.NodeType)} {value} ");
}
return builder.ToString();
} //解析表达式树
private void AnalysisExpressionChild(BinaryExpression expression, ExpressionType pType, string brackets = "")
{
if (expression.NodeType != ExpressionType.AndAlso && expression.NodeType != ExpressionType.OrElse)
{
var r = (ConstantExpression)expression.Right;
var l = (MemberExpression)expression.Left;
var value = r.Type.IsValueType ? r.Value : $"'{r.Value}'";
builder.Append($" {Operand(pType)} {brackets} { l.Member.Name} {Operand(expression.NodeType)} {value} ");
}
else
{
if (expression.NodeType == ExpressionType.OrElse)
{
brackets = "( ";
}
AnalysisExpressionChild((BinaryExpression)expression.Left, pType, brackets);
AnalysisExpressionChild((BinaryExpression)expression.Right, expression.NodeType);
if (expression.NodeType == ExpressionType.OrElse)
{
builder.Append(" )");
}
}
} //操作符转换
private string Operand(ExpressionType type)
{
string operand = string.Empty;
switch (type)
{
case ExpressionType.AndAlso:
operand = "AND";
break;
case ExpressionType.OrElse:
operand = "OR";
break;
case ExpressionType.Equal:
operand = "=";
break;
case ExpressionType.LessThan:
operand = "<";
break;
case ExpressionType.LessThanOrEqual:
operand = "<=";
break;
case ExpressionType.GreaterThan:
operand = ">";
break;
case ExpressionType.GreaterThanOrEqual:
operand = ">";
break;
}
return operand;
} }

至此,表达式树已经完成了解析,上面的案例已经能满足常用的需求了,若有其他要求我们可以继续改造拓展解析方法。

四、总结

我们在学习技术的时候带着一定的目的去学习往往效率更高,又不容易忘记,同时要善于思考,联系上下文情景。如果你觉得看完后对你有帮助可以给我点赞。

相关代码已经放到GitHub

参考文档:

1、微软官方文档库:https://docs.microsoft.com/zh-cn/dotnet/api/system.linq.expressions?view=netcore-2.2

2、腾讯云:https://cloud.tencent.com/developer/article/1334993

五、补充

经过继续研究和分析网友的解析方法,发现其实微软对表达式解析已经提供的了一个专门类:ExpressionVisitor,该类实现了对各种表达式操作的解析,我们直接继承它, 所以我又重写了一个解析类:AnalyseExpressionHelper,需要修改案例中调用解析方法的代码:

        private static string GetLambdaStr<T>(Expression<Func<T, bool>> expression)
{
//解析表达式三
var heler = new AnalyseExpressionHelper();
heler.AnalyseExpression(expression);
return heler.Result;
}

操作符转换函数已经修改为拓展方法:

 
using System.Linq.Expressions;

namespace ExpressionTreeDemo
{
public static class ExpressionExtend
{
//操作符转换
public static string TransferOperand(this ExpressionType type)
{
string operand = string.Empty;
switch (type)
{
case ExpressionType.AndAlso:
operand = "AND";
break;
case ExpressionType.OrElse:
operand = "OR";
break;
case ExpressionType.Equal:
operand = "=";
break;
case ExpressionType.NotEqual:
operand = "<>";
break;
case ExpressionType.LessThan:
operand = "<";
break;
case ExpressionType.LessThanOrEqual:
operand = "<=";
break;
case ExpressionType.GreaterThan:
operand = ">";
break;
case ExpressionType.GreaterThanOrEqual:
operand = ">=";
break;
}
return operand;
}
}
}

ExpressionExtend

新的表达式解析方法:

using System;
using System.Text;
using System.Linq.Expressions; namespace ExpressionTreeDemo
{
/// <summary>
/// 表达式解析辅助类
/// </summary>
public class AnalyseExpressionHelper : ExpressionVisitor
{
private StringBuilder express = new StringBuilder();
public string Result { get { return express.ToString(); } } public void AnalyseExpression<T>(Expression<Func<T, bool>> expression)
{
Visit(expression.Body);
} protected override Expression VisitBinary(BinaryExpression node)
{
if (node.NodeType == ExpressionType.OrElse)
express.Append("(");
Visit(node.Left);
express.Append($" {node.NodeType.TransferOperand()} ");
Visit(node.Right);
if (node.NodeType == ExpressionType.OrElse)
express.Append(")");
return node;
} protected override Expression VisitConstant(ConstantExpression node)
{
if (node.Type.IsValueType && node.Type != typeof(DateTime))
{
express.Append(node.Value);
}
else
{
express.Append($"'{node.Value}'");
}
return node;
} protected override Expression VisitMember(MemberExpression node)
{
express.Append(node.Member.Name);
return node;
}
}
}

AnalyseExpressionHelper

C#表达式树浅析的更多相关文章

  1. 再讲IQueryable<T>,揭开表达式树的神秘面纱

    接上篇<先说IEnumerable,我们每天用的foreach你真的懂它吗?> 最近园子里定制自己的orm那是一个风生水起,感觉不整个自己的orm都不好意思继续混博客园了(开个玩笑).那么 ...

  2. [C#] C# 知识回顾 - 表达式树 Expression Trees

    C# 知识回顾 - 表达式树 Expression Trees 目录 简介 Lambda 表达式创建表达式树 API 创建表达式树 解析表达式树 表达式树的永久性 编译表达式树 执行表达式树 修改表达 ...

  3. 轻量级表达式树解析框架Faller

    有话说 之前我写了3篇关于表达式树解析的文章 干货!表达式树解析"框架"(1) 干货!表达式树解析"框架"(2) 干货!表达式树解析"框架" ...

  4. 用五分钟重温委托,匿名方法,Lambda,泛型委托,表达式树

    这些对老一代的程序员都是老生常谈的东西,没什么新意,对新生代的程序员却充满着魅力.曾经新生代,好多都经过漫长的学习,理解,实践才能掌握委托,表达式树这些应用.今天我尝试用简单的方法叙述一下,让大家在五 ...

  5. LinqToDB 源码分析——处理表达式树

    处理表达式树可以说是所有要实现Linq To SQL的重点,同时他也是难点.笔者看完作者在LinqToDB框架里面对于这一部分的设计之后,心里有一点不知所然.由于很多代码没有文字注解.所以笔者只能接合 ...

  6. LinqToDB 源码分析——生成表达式树

    当我们知道了Linq查询要用到的数据库信息之后.接下就是生成对应的表达式树.在前面的章节里面笔者就已经介绍过.生成表达式树是事实离不开IQueryable<T>接口.而处理表达式树离不开I ...

  7. 干货!表达式树解析"框架"(1)

    最新设计请移步 轻量级表达式树解析框架Faller http://www.cnblogs.com/blqw/p/Faller.html 关于我和表达式树 其实我也没有深入了解表达式树一些内在实现的原理 ...

  8. 干货!表达式树解析"框架"(2)

    最新设计请移步 轻量级表达式树解析框架Faller http://www.cnblogs.com/blqw/p/Faller.html 为了过个好年,我还是赶快把这篇完成了吧 声明 本文内容需要有一定 ...

  9. 干货!表达式树解析"框架"(3)

    最新设计请移步 轻量级表达式树解析框架Faller http://www.cnblogs.com/blqw/p/Faller.html 这应该是年前最后一篇了,接下来的时间就要陪陪老婆孩子了 关于表达 ...

随机推荐

  1. 小埋的Dancing Line之旅:比赛题解&热身题题解

    答疑帖: 赞助团队:UMR IT Team和洛谷大佬栖息地 赛后题解:更新了那两道练手题的题解 赛时公告,不过一些通知也可能在团队宣言里发出 如果各位发现重题,请将你认为重复的题目链接连同这次比赛的题 ...

  2. Android解决RecyclerView中的item显示不全方案

    最近的项目中实现订单确定页面.需要使用ScrollView嵌套RecyclerView,当RecyclerView中的item数量比较多时,就会出现item只显示一部分数据,并没有将用户勾选的商品数量 ...

  3. Javascript中style,currentStyle和getComputedStyle的区别以及获取css操作方法

    style: 只能获取行内style. 调用:obj.style.属性; 兼容:都兼容 currentStyle: 可以获取该obj所有style,但只可读. 调用:obj.currentStyle[ ...

  4. 小白开学Asp.Net Core《三》

    小白开学Asp.Net Core<三> ——界面 我胡汉三再次又回来了(距离上篇时间有点长),今天抽时间将最近对框架采用的后台界面做个记录 1.先上图 (图一) (图二) 2.界面说明 后 ...

  5. 《PHP从入门到精通(第3版)》目录

    一.基础知识 1.初识PHP 2.PHP环境搭建和开发工具 3.PHP语言基础 4.流程控制语句 5.字符串操作 6.正则表达式 7.PHP数组 8.PHP与Web页面交互 9.PHP与JavaScr ...

  6. Java创建线程的两个方法

    Java提供了线程类Thread来创建多线程的程序.其实,创建线程与创建普通的类的对象的操作是一样的,而线程就是Thread类或其子类的实例对象.每个Thread对象描述了一个单独的线程.要产生一个线 ...

  7. AbstractList

    概述 此类提供 List 接口的骨干实现,以最大限度地减少实现“随机访问”数据存储(如数组)支持的该接口所需的工作.对于连续的访问数据(如链表),应优先使用 AbstractSequentialLis ...

  8. 泥瓦匠 5 年 Java 的成长感悟(下)

    继续<泥瓦匠 5 年 Java 的成长感悟(上)>,大致包括下面几点: 学技术的心态 学技术的学法 工作的心态 工作的硬技能 工作的软实力 听点雷子的民谣,我就安静地感概感概.上次说写的, ...

  9. Selenium+java - 借助autolt完成上传文件操作

    写在前面: 上传文件是每个自动化测试同学会遇到,而且可以说是面试必考的问题,标准控件我们一般用sendkeys()就能完成上传,但是我们的测试网站的上传控件一般为自己封装的,用传统的上传已经不好用了, ...

  10. 【Java例题】4.4使用牛顿迭代法求方程的解

    4. 使用牛顿迭代法求方程的解:x^3-2x-5=0区间为[2,3]这里的"^"表示乘方. package chapter4; public class demo4 { publi ...