C#表达式树浅析

一、前言

     在我们日常开发中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;}
View Code

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

三、进阶

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

            //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 > 30 || n.ID == 4) && n.ID > 1
&& (n.ID > 19 || n.Name == "33"));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 > 30 || n.ID == 4) && n.ID > 1 && (n.ID > 19 || n.Name == "33"));Console.WriteLine("result:" + c);Console.Write(Environment.NewLine);Console.ReadKey();
View Code

2、解析函数

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

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

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

四、总结

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

 

相关代码已经放到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

 

转载于:https://www.cnblogs.com/heweijian/p/11406715.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/262807.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

下载 sdk struts java

<action name"sdkDownload" class"com.curiousby.sdkDownload"><!-- result的Type必须为stream --><result name"success" type"stream"><param name"contentType">application/octet-stream;char…

微信小程序省市区联动,自定义地区字典

最近在做一个项目的时候遇到了这么一个问题&#xff0c;就是省市区的联动呢&#xff0c;我们需要自定义字典来设置&#xff0c;那么微信小程序自带的省市区选择就不能用了&#xff0c;经过三根烟的催化&#xff0c;终于写出来了。下面献上代码示例。 首先是在utils文件夹存入ar…

论文翻译《Object-Level Ranking: Bringing Order to Web Objects》

Object-Level Ranking: Bringing Order to Web Objects Zaiqing Nie Yuanzhi Zhang Jirong Wen Weiying Ma 摘要&#xff1a; 现在的网络搜索方法实际上是做文档级排名和检索&#xff0c;与之相对比&#xff0c;我们在探索一种新的聚合体以实现在对象级的网络检索。我们搜集与某…

前端vscode常用插件

Auto Rename Tag 这是一个html标签的插件&#xff0c;可以让你修改一边标签&#xff0c;另外一边自动改变。 Beautify 格式化代码插件 Braket Pair Colorizer 给js文件中的每一个小括号()花括号{}都配上不同的颜色&#xff0c;方便找到哪一个位置多了少了括号。 Debugger for C…

在线条形码生成器

条形码又称条码、一维码&#xff0c;是将字符按照特定的规则转化成二进制后&#xff0c;描绘成一个宽度不等的多个黑条和空白&#xff0c;按照一定的编码规则排列的图形标识符&#xff0c;条形码现在应用相当广泛&#xff0c;一出门&#xff0c;随便翻一样东西&#xff0c;可能…

[JSOI2008 Prefix火星人]

[关键字]&#xff1a;splay hash 二分 [题目大意]&#xff1a;给出一个字符串&#xff0c;求出给定的两个后缀的的最长公共前缀。在求的过程中会有改变或在某个位置添加字符的操作。 // [分析]&#xff1a;一听最长公共前缀马上想到后缀数组&#xff0c;但因为是动态维护所以后…

ios学习笔记block回调的应用(一个简单的例子)

一、什么是Blocks Block是一个C级别的语法以及运行时的一个特性&#xff0c;和标准C中的函数&#xff08;函数指针&#xff09;类似&#xff0c;但是其运行需要编译器和运行时支持&#xff0c;从ios4.0开始就很好的支持Block。 二、在ios开发中&#xff0c;什么情况下使用…

vue定义global.js,挂载在vue原型上面使用

首先在src目录下创建global目录&#xff0c;在global目录下创建index.js。 export default {install(Vue) {var that this// 1. 添加全局方法或属性// ue.global this// 2. 添加全局资源// 3. 注入组件Vue.mixin({created() {this.global that}})// 大于一的整数验证&#x…

《Windows游戏编程大师技巧》三、Windows高级编程

Windows编程很绝的地方在于&#xff1a;你不用了解太多细节&#xff0c;就可以完成很多工作。使用资源资源就是你的程序代码结合在一起的多块数据&#xff0c;可以被程序本身在运行时加载。资源应当也放在程序的.EXE文件中的原因是&#xff1a;1.同时包含代码和数据的.EXE文件更…

结构型模式--装饰模式

下面先用java&#xff0c;然后用Objective&#xff0d;C行对装饰模式的讲解&#xff1a; 对于java的装饰模式讲解和使用比较详细和难度有点偏高&#xff0c;而对于Objective&#xff0d;C的装饰模式讲解和使用方面比较简单&#xff0c;而且和java的装饰模式略有差异&#xff0c…

ArcGIS.Server.9.2.DotNet自带例子分析(三、一)

目的&#xff1a; 1.arcgis server9.2 ADF的AddGraphics。 准备工作&#xff1a; 1.用ArcGis Server Manager或者ArcCatalog发布一个叫world的Map Service,并且把这个Service启动起来。 2.找到DeveloperKit\SamplesNET\Server\Web_Applications目录下的Common_AddGraphicsCShar…

aspose将datatable导出excel 比自己拼好的多 Bug少-。.net

using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.IO; using System.Data; using Aspose.Cells; /// <summary> ///OutFileDao 的摘要说明 /// </summary> publicclass OutFileDao { public OutFileDa…

整理:Android apk 框架 布局 集锦

2019独角兽企业重金招聘Python工程师标准>>> 看到好的技术教程就想分享一下&#xff0c;不喜勿喷&#xff01;谢谢配合&#xff0c;仅供菜鸟学习研究(^o^)/~ 友情推荐《爱加密》Android apk加密保护视频教程剪辑&#xff1a;http://www.ijiami.cn/Video?v3 Andro…

IE8不兼容你的网页 怎么办? - 简单开启兼容模式

自从用了IE8 整个世界都变了形.   呵呵,问题没那么严重,如果你的网站还来不及修改以适合IE8访问的时候,咱们可以通过非常简单的方法,加几行代码就可以让访问的IE8自动调用IE7的渲染模式[/b],这样可以保证最大的兼容性,方法如下:   只需要在页面中加入如下HTTP meta-tag:  …

springboot打war包汇总

概述 第一次用maven工具打war包&#xff0c;出现各种各样的问题&#xff0c;做个问题记录方便下次查看 maven 一开始用的maven是springboot默认的&#xff0c;在.m2下&#xff0c;要打包时才发现没有mvn指令。索性自己就重新装个maven&#xff0c;去官网下载&#xff0c;我安装…

大数据初探——Hadoop历史

Hadoop是一个开源的分布式框架&#xff0c;是Apache下的一个开源项目。Hadoop运行可以在成千上万个普通机器节点组成的集群上&#xff0c;通过分布式的计算模型和存储模型来处理大数据集。Hadoop具有高容错性、工作在普通的机器节点上扩展性强等众多的优点&#xff0c;是企业选…

BMP格式图像的显示

使用多文档编程 也可以使用单文档编程 建立一个DIB图像的显示类 ImageDib 成员变量&#xff1a; 4个指针&#xff1a; LPBYTE m_lpDib; //指向DIB的指针    LPBITMAPINFOHEADER m_lpBmpInfoHead; //图像信息头指针 LPRGBQUAD m_lpColorTable; //图像颜色表指针 …

深入A*算法

一、前言 在这里我将对A*算法的实际应用进行一定的探讨&#xff0c;并且举一个有关A*算法在最短路径搜索的例子。 二、A*算法的程序编写原理 A*算法是最好优先算法的一种。只是有一些约束条件而已。我们先来看看最好优先算法是如何编写的吧。 如图有如下的状态空间&#xff1a;…

IOS中NSUserDefaults的用法

2019独角兽企业重金招聘Python工程师标准>>> IOS中NSUserDefaults的用法&#xff08;轻量级本地数据存储&#xff09; 分类&#xff1a; IOS开发 Object&#xff0d;C编程语言2012-09-09 10:58 65223人阅读 评论(13) 收藏 举报 存储iosfloatinterfaceintegerdate NS…

【Oracle 学习笔记】Day 1 常用函数整理(转换、DeCode),表的外键

select Convert(varchar,Convert(money,TaxExValue),1) from A--Result 2,794.87 58,119.66 1,367.52 对于SQL Server来说&#xff0c;进行金额的转换&#xff0c;可以按照上面的操作那样&#xff0c;会自动将金额处理为两位小数&#xff0c;并用逗号分隔小数点前面的数字。 当…