当前位置: 首页 > news >正文

.NET实现解析字符串表达式

一、引子·功能需求

我们创建了一个 School 对象,其中包含了教师列表和学生列表。现在,我们需要计算教师平均年龄和学生平均年龄。

//创建对象
School school = new School()
{Name = "小菜学园",Teachers = new List<Teacher>(){new Teacher() {Name="波老师",Age=26},new Teacher() {Name="仓老师",Age=28},new Teacher() {Name="悠老师",Age=30},},Students=  new List<Student>(){new Student() {Name="小赵",Age=22},new Student() {Name="小钱",Age=23},new Student() {Name="小孙",Age=24},},//这两个值如何计算?TeachersAvgAge = "",StudentsAvgAge = "",
};

如果我们将计算教师平均年龄的公式交给用户定义,那么用户可能会定义一个字符串来表示:

Teachers.Sum(Age)/Teachers.Count

或者可以通过lambda来表示:

teachers.Average(teacher => teacher.Age)

此时我们就获得了字符串类型的表达式,如何进行解析呢?

二、构建字符串表达式

手动构造

这种方式是使用 Expression 类手动构建表达式,虽然不符合我们的实际需求,但是它是Dynamic.Core底层实现的方式。Expression 类的文档地址为::Expression 类 (System.Linq.Expressions) | Microsoft Learn

// 创建参数表达式
var teachersParam = Expression.Parameter(typeof(Teacher[]), "teachers");// 创建变量表达式
var teacherVar = Expression.Variable(typeof(Teacher), "teacher");// 创建 lambda 表达式
var lambdaExpr = Expression.Lambda<Func<Teacher[], double>>(Expression.Block(new[] { teacherVar }, // 定义变量Expression.Call(typeof(Enumerable),"Average",new[] { typeof(Teacher) },teachersParam,Expression.Lambda(Expression.Property(teacherVar, // 使用变量nameof(Teacher.Age)),teacherVar // 使用变量))),teachersParam
);// 编译表达式树为委托
var func = lambdaExpr.Compile();var avgAge = func(teachers);

使用System.Linq.Dynamic.Core

System.Linq.Dynamic.Core 是一个开源库,它提供了在运行时构建和解析 Lambda 表达式树的功能。它的原理是使用 C# 语言本身的语法和类型系统来表示表达式,并通过解析和编译代码字符串来生成表达式树。

// 构造 lambda 表达式的字符串形式
string exprString = "teachers.Average(teacher => teacher.Age)";// 解析 lambda 表达式字符串,生成表达式树
var parameter = Expression.Parameter(typeof(Teacher[]), "teachers");
var lambdaExpr = DynamicExpressionParser.ParseLambda(new[] { parameter }, typeof(double), exprString);// 编译表达式树为委托
var func = (Func<Teacher[], double>)lambdaExpr.Compile();// 计算教师平均年龄
var avgAge = func(teachers);

三、介绍System.Linq.Dynamic.Core

使用此动态 LINQ 库,我们可以执行以下操作:

  • 通过 LINQ 提供程序进行的基于字符串的动态查询。
  • 动态分析字符串以生成表达式树,例如ParseLambda和Parse方法。
  • 使用CreateType方法动态创建数据类。

功能介绍

普通的功能此处不赘述,如果感兴趣,可以从下文提供文档地址去寻找使用案例。

  1. 添加自定义方法类

可以通过在静态帮助程序/实用工具类中定义一些其他逻辑来扩展动态 LINQ 的分析功能。为了能够做到这一点,有几个要求:

  • 该类必须是公共静态类
  • 此类中的方法也需要是公共的和静态的
  • 类本身需要使用属性进行注释[DynamicLinqType]
[DynamicLinqType]
public static class Utils
{public static int ParseAsInt(string value){if (value == null){return 0;}return int.Parse(value);}public static int IncrementMe(this int values){return values + 1;}
}

此类有两个简单的方法:

当输入字符串为 null 时返回整数值 0,否则将字符串解析为整数
使用扩展方法递增整数值

用法:

var query = new [] { new { Value = (string) null }, new { Value = "100" } }.AsQueryable();
var result = query.Select("Utils.ParseAsInt(Value)");

除了以上添加[DynamicLinqType]属性这样的方法,我们还可以在配置中添加。

public class MyCustomTypeProvider : DefaultDynamicLinqCustomTypeProvider
{public override HashSet<Type> GetCustomTypes() =>new[] { typeof(Utils)}.ToHashSet();
}

文档地址

  • 源码地址:GitHub - zzzprojects/System.Linq.Dynamic.Core: The .NET Standard / .NET Core version from the System Linq Dynamic functionality.
  • 文档地址:Overview in Dynamic LINQ

使用项目

  • 规则引擎RulesEngine中解析表达式的实现:Home · microsoft/RulesEngine Wiki · GitHub
  • 自己封装了低代码中公式编辑器中公式的解析功能

四、浅析System.Linq.Dynamic.Core

System.Linq.Dynamic.Core中 DynamicExpressionParser 和 ExpressionParser 都是用于解析字符串表达式并生成 Lambda 表达式树的类,但它们之间有一些不同之处。

ExpressionParser 类支持解析任何合法的 C# 表达式,并生成对应的表达式树。这意味着您可以在表达式中使用各种运算符、方法调用、属性访问等特性。

DynamicExpressionParser 类则更加灵活和通用。它支持解析任何语言的表达式,包括动态语言和自定义 DSL(领域特定语言)

我们先看ExpressionParser这个类,它用于解析字符串表达式并生成 Lambda 表达式树。

我只抽取重要的和自己感兴趣的属性和方法。

  • TextParser 类,实现算法有点类似于有限状态自动机(FSM): 力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台
  • MethodFinder,使用了反射机制,通过调用 GetMethods() 方法获取指定类型中定义的所有方法,并根据参数数量和类型等条件检查参数是否符合特定的条件。如果参数满足了条件,则将该方法添加到结果列表中。
public class ExpressionParser
{//字符串解析器的配置,比如区分大小写、是否自动解析类型、自定义类型解析器等private readonly ParsingConfig _parsingConfig;//查找指定类型中的方法信息,通过反射获取MethodInfoprivate readonly MethodFinder _methodFinder;//用于帮助解析器识别关键字、操作符和常量值private readonly IKeywordsHelper _keywordsHelper;//解析字符串表达式中的文本,用于从字符串中读取字符、单词、数字等private readonly TextParser _textParser;//解析字符串表达式中的数字,用于将字符串转换为各种数字类型private readonly NumberParser _numberParser;//用于帮助生成和操作表达式树private readonly IExpressionHelper _expressionHelper;//用于查找指定名称的类型信息private readonly ITypeFinder _typeFinder;//用于创建类型转换器private readonly ITypeConverterFactory _typeConverterFactory;//用于存储解析器内部使用的变量和选项。这些变量和选项不应该由外部代码访问或修改private readonly Dictionary<string, object> _internals = new();//用于存储字符串表达式中使用的符号和值。例如,如果表达式包含 @0 占位符,则可以使用 _symbols["@0"] 访问其值。private readonly Dictionary<string, object?> _symbols;//表示外部传入的参数和变量。如果表达式需要引用外部的参数或变量,则应该将它们添加到 _externals 中。private IDictionary<string, object>? _externals;/// <summary>/// 使用TextParser将字符串解析为指定的结果类型./// </summary>/// <param name="resultType"></param>/// <param name="createParameterCtor">是否创建带有相同名称的构造函数</param>/// <returns>Expression</returns>public Expression Parse(Type? resultType, bool createParameterCtor = true){_resultType = resultType;_createParameterCtor = createParameterCtor;int exprPos = _textParser.CurrentToken.Pos;//解析条件运算符表达式Expression? expr = ParseConditionalOperator();//将返回的表达式提升为指定类型if (resultType != null){if ((expr = _parsingConfig.ExpressionPromoter.Promote(expr, resultType, true, false)) == null){throw ParseError(exprPos, Res.ExpressionTypeMismatch, TypeHelper.GetTypeName(resultType));}}//验证最后一个标记是否为 TokenId.End,否则抛出语法错误异常_textParser.ValidateToken(TokenId.End, Res.SyntaxError);return expr;}// ?: operatorprivate Expression ParseConditionalOperator(){int errorPos = _textParser.CurrentToken.Pos;Expression expr = ParseNullCoalescingOperator();if (_textParser.CurrentToken.Id == TokenId.Question){......}return expr;}// ?? (null-coalescing) operatorprivate Expression ParseNullCoalescingOperator(){Expression expr = ParseLambdaOperator();......return expr;}// => operator - Added Support for projection operatorprivate Expression ParseLambdaOperator(){Expression expr = ParseOrOperator();......return expr;}}

相关文章:

.NET实现解析字符串表达式

一、引子功能需求 我们创建了一个 School 对象&#xff0c;其中包含了教师列表和学生列表。现在&#xff0c;我们需要计算教师平均年龄和学生平均年龄。 //创建对象 School school new School() {Name "小菜学园",Teachers new List<Teacher>(){new Teach…...

Ae 效果:CC Environment

透视/CC Environment Perspective/CC Environment CC Environment&#xff08;CC 环境&#xff09;主要用于创建 3D 环境映射&#xff0c;可以将一个 2D 图像转换为 3D 空间的反射或折射。该效果通常用于模拟真实世界的全景相机镜头和环境反射。 在实际操作中&#xff0c;可将效…...

PotgreSQL 基于时间点恢复

文章目录 前言1. WAL 日志1.1 WAL 配置 2. pg_basebackup2.1 常用命令 3. 全备恢复实验3.1 进行一个全备3.2 写入增量数据3.3 模拟备份恢复 总结 前言 本篇文章介绍 PostgreSQL 基于时间点恢复&#xff08;point-in-time-recover&#xff09;需要的条件及恢复过程&#xff0c;…...

③ vue组件

vue组件创建 在App.vue中添加。 技巧&#xff1a;先import&#xff0c;把vue组件地址写出来。然后在template中写名字。剩下的就自动生成。要看下import有没有多生成什么。 注意1&#xff1a; 注意2&#xff1a; 不只是能在App.vue中引入组件。任意组件中都可以引用其他组件…...

政策因子条件列表类型

type:1&#xff08;多选下拉输入&#xff09; 目前选项是本地写死得&#xff0c;传参拼接了carOwner这些东西&#xff0c;形式是多选输入 type:2 &#xff08;普通指定形式&#xff09; type:3&#xff08;普通区间形式&#xff09; type:4 (指定输入) 指定除外得特殊一种&…...

python中yield关键字

yield和return 理解一个东西最好的办法就是找一个和它类似的东西&#xff0c;然后再搞清楚它们之间的区别。 yield最类似的东西就是return&#xff0c;因为他们起到了同样的作用&#xff1a;返回值。 看这个return的函数&#xff1a; def have_some_wine():print(先开一瓶酒&a…...

2023年10款常用的Mac工具合集

Typora Typora 是一款由 Abner Lee 开发的轻量级 Markdown 编辑器&#xff0c;与其他 Markdown 编辑器不同的是&#xff0c;Typora 没有采用源代码和预览双栏显示的方式&#xff0c;而是采用所见即所得的编辑方式&#xff0c;实现了即时预览的功能&#xff0c;但也可切换至源代…...

Python爬虫——requests_cookie登陆古诗文网

寻找登陆需要的参数 __VIEWSTATE:aiMG0UXAfCzak10C7436ZC/RXoZbM2lDlX1iU/4wjjdUNsW8QUs6W2/3M6XIKagQZrC7ooD8Upj8uCnpQMXjDAp6fS/NM2nGhnKO0KOSXfT3jGHhJAOBouMI3QnlpJCQKPXfVDJPYwh169MGLFC6trY __VIEWSTATEGENERATOR: C93BE1AE from: http://so.gushiwen.cn/user/collect.…...

C++——继承

文章目录 &#x1f99c;1. 什么是继承&#x1f40a;1.1 概念&#x1f40a;1.2 格式&#x1f40a;1.3 继承方式 & 访问限定符 &#x1f426;2. 派生类和基类的赋值问题&#x1f9a9;3. 派生类和基类同名成员问题&#x1f413;4.派生类默认成员函数&#x1f409;4.1 构造函数…...

openGauss学习笔记-38 openGauss 高级数据管理-游标

文章目录 openGauss学习笔记-38 openGauss 高级数据管理-游标38.1 语法格式38.2 参数说明38.3 示例 openGauss学习笔记-38 openGauss 高级数据管理-游标 为了处理SQL语句&#xff0c;存储过程进程分配一段内存区域来保存上下文联系。游标是指向上下文区域的句柄或指针。借助游…...

Blazor简单教程(1.1):Razor基础语法

文章目录 前言基本文件配置引入Layout组件 语法介绍pagecodeRazor 语法[ 显式表达和隐式表达](https://learn.microsoft.com/zh-cn/aspnet/core/mvc/views/razor?viewaspnetcore-7.0#explicit-razor-expressions) 绑定简单绑定双向绑定带参数的函数绑定 依赖注入 前言 Blazor…...

Leetcode-每日一题【剑指 Offer 25. 合并两个排序的链表】

题目 输入两个递增排序的链表&#xff0c;合并这两个链表并使新链表中的节点仍然是递增排序的。 示例1&#xff1a; 输入&#xff1a;1->2->4, 1->3->4输出&#xff1a;1->1->2->3->4->4 限制&#xff1a; 0 < 链表长度 < 1000 解题思路 1…...

前端盒子模型

day05-盒子模型 目标&#xff1a;掌握盒子模型组成部分&#xff0c;使用盒子模型布局网页区域 01-选择器 结构伪类选择器 基本使用 作用&#xff1a;根据元素的结构关系查找元素。 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lNhfXFEc-169194…...

【ARM Cache 系列文章 9 -- ARM big.LITTLE技术】

文章目录 big.LITTLE 技术背景big.LITTLE 技术详解big.LITTLE 硬件要求 big.LITTLE 软件模型CPU MigrationGlobal Task SchedulingGlobal Task Scheduling比CPU Migration的优势 转自&#xff1a;https://zhuanlan.zhihu.com/p/630981648 如有侵权&#xff0c;请联系删除 big.L…...

【BASH】回顾与知识点梳理(十四)

【BASH】回顾与知识点梳理 十四 十四. 文件与目录的默认权限与隐藏权限14.1 文件预设权限&#xff1a;umaskumask 的利用与重要性&#xff1a;专题制作 14.2 文件隐藏属性chattr (配置文件案隐藏属性)lsattr (显示文件隐藏属性) 14.3 文件特殊权限&#xff1a; SUID, SGID, SBI…...

乡村振兴指数与其30余个原始变量数据(2000-2022年)

乡村振兴是当下经济学研究的热点之一&#xff0c;对乡村振兴进行测度&#xff0c;是研究基础。测度乡村振兴水平的学术论文广泛发表在《数量经济技术经济研究》等顶刊上。整理了2000-2022年城市层面的乡村振兴指数与其30余个原始变量数据&#xff0c;供大家使用。 数据来源&…...

深入理解MySQL表的操作和管理

MySQL是一种广泛使用的关系型数据库管理系统&#xff0c;用于存储和管理大量结构化数据。在MySQL中&#xff0c;表是数据的基本组织单位&#xff0c;对表的操作和管理能力对于数据库的性能和数据完整性至关重要。本文将深入讨论MySQL表的操作和管理&#xff0c;包括创建表、修改…...

MATLAB图论合集(一)基本操作基础

本帖总结一些经典的图论问题&#xff0c;通过MATLAB如何计算答案。近期在复习考研&#xff0c;以此来巩固一下相关知识——虽然考研肯定不能用MATLAB代码哈哈&#xff0c;不过在实际应用中解决问题还是很不错的&#xff0c;比C易上手得多~ 图论中的图&#xff08;Graph&#xf…...

Redis布隆过滤器的原理和应用场景,解决缓存穿透

目录 一、redis 二、布隆过滤器 三、缓存穿透问题 四、布隆过滤器解决缓存穿透 一、redis Redis&#xff08;Remote Dictionary Server&#xff09;是一种开源的内存数据存储系统&#xff0c;也是一个使用键值对&#xff08;Key-Value&#xff09;方式的高性能数据库。Red…...

数据结构中的英文缩写和易忘的专有名词

树与二叉树 满二叉树&#xff1a;每层的节点数都是该层能取到的最大值完全二叉树&#xff1a;在满二叉树的基础上&#xff0c;去掉最下层右面的一些连续叶子节点所形成的二叉树AVL&#xff1a;平衡二叉树。树中任意节点的左子树和右子树高度之差的绝对值不超过1的二叉排序树称…...

Admin.Net中的消息通信SignalR解释

定义集线器接口 IOnlineUserHub public interface IOnlineUserHub {/// 在线用户列表Task OnlineUserList(OnlineUserList context);/// 强制下线Task ForceOffline(object context);/// 发布站内消息Task PublicNotice(SysNotice context);/// 接收消息Task ReceiveMessage(…...

MongoDB学习和应用(高效的非关系型数据库)

一丶 MongoDB简介 对于社交类软件的功能&#xff0c;我们需要对它的功能特点进行分析&#xff1a; 数据量会随着用户数增大而增大读多写少价值较低非好友看不到其动态信息地理位置的查询… 针对以上特点进行分析各大存储工具&#xff1a; mysql&#xff1a;关系型数据库&am…...

IGP(Interior Gateway Protocol,内部网关协议)

IGP&#xff08;Interior Gateway Protocol&#xff0c;内部网关协议&#xff09; 是一种用于在一个自治系统&#xff08;AS&#xff09;内部传递路由信息的路由协议&#xff0c;主要用于在一个组织或机构的内部网络中决定数据包的最佳路径。与用于自治系统之间通信的 EGP&…...

在Ubuntu中设置开机自动运行(sudo)指令的指南

在Ubuntu系统中&#xff0c;有时需要在系统启动时自动执行某些命令&#xff0c;特别是需要 sudo权限的指令。为了实现这一功能&#xff0c;可以使用多种方法&#xff0c;包括编写Systemd服务、配置 rc.local文件或使用 cron任务计划。本文将详细介绍这些方法&#xff0c;并提供…...

Neo4j 集群管理:原理、技术与最佳实践深度解析

Neo4j 的集群技术是其企业级高可用性、可扩展性和容错能力的核心。通过深入分析官方文档,本文将系统阐述其集群管理的核心原理、关键技术、实用技巧和行业最佳实践。 Neo4j 的 Causal Clustering 架构提供了一个强大而灵活的基石,用于构建高可用、可扩展且一致的图数据库服务…...

VTK如何让部分单位不可见

最近遇到一个需求&#xff0c;需要让一个vtkDataSet中的部分单元不可见&#xff0c;查阅了一些资料大概有以下几种方式 1.通过颜色映射表来进行&#xff0c;是最正规的做法 vtkNew<vtkLookupTable> lut; //值为0不显示&#xff0c;主要是最后一个参数&#xff0c;透明度…...

C++.OpenGL (10/64)基础光照(Basic Lighting)

基础光照(Basic Lighting) 冯氏光照模型(Phong Lighting Model) #mermaid-svg-GLdskXwWINxNGHso {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-GLdskXwWINxNGHso .error-icon{fill:#552222;}#mermaid-svg-GLd…...

Android 之 kotlin 语言学习笔记三(Kotlin-Java 互操作)

参考官方文档&#xff1a;https://developer.android.google.cn/kotlin/interop?hlzh-cn 一、Java&#xff08;供 Kotlin 使用&#xff09; 1、不得使用硬关键字 不要使用 Kotlin 的任何硬关键字作为方法的名称 或字段。允许使用 Kotlin 的软关键字、修饰符关键字和特殊标识…...

AI书签管理工具开发全记录(十九):嵌入资源处理

1.前言 &#x1f4dd; 在上一篇文章中&#xff0c;我们完成了书签的导入导出功能。本篇文章我们研究如何处理嵌入资源&#xff0c;方便后续将资源打包到一个可执行文件中。 2.embed介绍 &#x1f3af; Go 1.16 引入了革命性的 embed 包&#xff0c;彻底改变了静态资源管理的…...

企业如何增强终端安全?

在数字化转型加速的今天&#xff0c;企业的业务运行越来越依赖于终端设备。从员工的笔记本电脑、智能手机&#xff0c;到工厂里的物联网设备、智能传感器&#xff0c;这些终端构成了企业与外部世界连接的 “神经末梢”。然而&#xff0c;随着远程办公的常态化和设备接入的爆炸式…...