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

news/2024/9/12 18:13:03/

一、引子·功能需求

我们创建了一个 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;}}

http://www.ppmy.cn/news/1027814.html

相关文章

设备工单管理系统如何实现工单流程自动化?

设备工单管理系统属于工单系统的一种&#xff0c;基于其丰富的功能&#xff0c;它可以同时处理不同的多组流程&#xff0c;旨在有效处理发起人提交的事情&#xff0c;指派相应人员完成服务请求和记录全流程。该系统主要面向后勤管理、设备维护、物业管理、酒店民宿等服务行业设…

侯捷 C++ part2 兼谈对象模型笔记——4 specialization 特化

4. specialization 特化 4.1 全特化 full specialization 模板是泛化&#xff0c;特化是泛化的反面&#xff0c;可以针对不同的类型&#xff0c;来设计不同的东西 其语法为template<> struct xxx<type> template<> struct hash<char> { ...size_t …

OptaPlanner笔记6 N皇后

N 个皇后 问题描述 将n个皇后放在n大小的棋盘上&#xff0c;没有两个皇后可以互相攻击。 最常见的 n 个皇后谜题是八个皇后谜题&#xff0c;n 8&#xff1a; 约束&#xff1a; 使用 n 列和 n 行的棋盘。在棋盘上放置n个皇后。没有两个女王可以互相攻击。女王可以攻击同一水…

2023 年牛客多校第七场题解

A Random Addition 题意&#xff1a;给定长度为 n n n 的数列&#xff0c;初始全为 0 0 0。对其中 m m m 个区间 [ l i , r i ] [l_i,r_i] [li​,ri​] 执行加 x x x 操作&#xff0c; x x x 等概率从 [ 0 , 1 ] [0,1] [0,1] 实数集合选取。这些区间包含或不相交。 q q …

渗透测试验证码爆破实操

一、准备工具 captcha-killer-modified 下载路径 https://github.com/f0ng/captcha-killer-modified https://github.com/f0ng/captcha-killer-modified/releases/download/0.21-beta/captcha-killer-modified-0.21-beta-jdk11.jar 二、安装工具 burp安装captcha-killer-modif…

【ARM 嵌入式 编译系列 4.1 -- GCC 编译属性 likely与unlikely 学习】

文章目录 GCC likely与unlikely 介绍linux 内核中的 likely/unlikely 上篇文章&#xff1a;ARM 嵌入式 编译系列 4 – GCC 编译属性 __read_mostly 介绍 下篇文章: ARM 嵌入式 编译系列 4.2 – GCC 链接规范 extern “C“ 介绍 GCC likely与unlikely 介绍 likely 和 unlikely …

初步制作做一个AI智能工具网站,持续更新

文章目录 介绍AI对话AI绘画AI音视频AI图片处理AI小工具体验 介绍 网页有五大部分&#xff1a;AI对话、AI绘画、AI音视频、AI 图片处理、AI小工具。 AI对话 AI对话是指人工智能技术在模拟人类对话交流方面的应用。通过使用自然语言处理和机器学习算法&#xff0c;AI对话系统可…

Keystone Automotive EDI 需求分析

Keystone Automotive 是一家知名的汽车零部件销售卖场&#xff0c;自创立以来&#xff0c;在汽车行业取得了卓越的成就。作为一家专业的汽车零部件供应商&#xff0c;Keystone Automotive 致力于为客户提供优质的产品和卓越的服务。公司的经营范围涵盖广泛&#xff0c;涉及多个…

【硬件突击 电路】

文章目录 1. 电阻&#xff08;Resistor&#xff09;&#xff1a;2. 电容&#xff08;Capacitor&#xff09;&#xff1a;3. 电感&#xff1a;4、 RC、RL、RLC电路结构及工作原理基尔霍夫定律基尔霍夫电流定律&#xff08;KCL&#xff09;基尔霍夫电压定律&#xff08;KVL&#…

后端 .net7 Minimal API 限流中间件(微信小程序无师自通十)

我的微信小程序使用.net7 Minimal API 作为后端&#xff0c;当服务器摆上公网后&#xff0c;可以观察到很多的攻击行为和暴力访问。所以&#xff0c;我需要使用微软的限流中间件部署相应的功能在服务器上 关键字&#xff1a; AddFixedWindowLimiter using Microsoft.AspNetCo…

【python】爬取豆瓣电影Top250(附源码)

前言 在网络爬虫的开发过程中&#xff0c;经常会遇到需要处理一些反爬机制的情况。其中之一就是网站对于频繁访问的限制&#xff0c;即IP封禁。为了绕过这种限制&#xff0c;我们可以使用代理IP来动态改变请求的来源IP地址。在本篇博客中&#xff0c;将介绍如何使用代理IP的技术…

STM32入门学习之定时器输入捕获

1.定时器的输入捕获可以用来测量脉冲宽度或者测量频率。输入捕获的原理图如下&#xff1a; 假设定时器是向上计数。在图中&#xff0c;t1~t2之间的便是我们要测量的高电平的时间(脉冲宽度)。首先&#xff0c;设置定时器为上升沿捕获&#xff0c;如此一来&#xff0c;在t1时刻可…

SpringBoot复习(39)Servlet容器的自动配置原理

Servlet容器自动配置类为ServletWebServerFactoryAutoConfiguration 可以看到通过Import注解导入了三个配置类&#xff1a; 通过这个这三个配置类可以看出&#xff0c;它们都使用了ConditionalOnClass注解&#xff0c;当类路径存在tomcat相关的类时&#xff0c;会配置一个T…

Atomic实现:变量的原子操作

文章目录 使用场景核心实现逻辑atomic实现思考参考 使用场景 多个goroutine里面安全访问共享变量 核心实现逻辑 通过系统的CPU指令来保证对变量的原子操作&#xff0c;如i386的LOCK指令 atomic实现 代码主要包含如下功能&#xff0c;实际为三种类型&#xff0c;通用类型Val…

python中range()函数详解(含反序输出)

目录 前言 语法 参数 返回值 示例 反序 前言 range() 函数是 Python 内置的用于生成整数序列的函数。它可以用于 for 循环、列表生成式等场景。下面是 range() 函数的详细说明&#xff1a; 语法 range(stop) range(start, stop[, step]) 参数 start&#xff08;可选…

Spark 3.4.x 对 from_json regexp_replace组合表达式慢问题的解决

背景 在Spark 3.1.1 遇到的 from_json regexp_replace组合表达式慢问题的解决 中其实在spark 3.4.x已经解决了&#xff0c; 具体的解决方法可以见 SPARK-44700&#xff0c; 也就是设置spark.sql.optimizer.collapseProjectAlwaysInline 为 false &#xff08;默认就是false&a…

隐藏挖矿木马rcu_tasked的查杀

0. 目录 1. 前言2. 排查2.1 找出导致问题的进程2.2 定位进程详细信息2.3 阻止进程重启2.4 重启服务器 3. 加固系统4. 其他相关排查手段5. 相关 1. 前言 本来没打算写这篇博客的&#xff0c;因为本身自己对于linux就属于一知半解&#xff0c;但奈何类似的问题遇到了好几次&…

ES索引重建reindex详解

目录 一、使用场景 二、reindex介绍 三、使用手册 1、覆盖更新 2、创建丢失的文档并更新旧版本的文档 3、仅创建丢失的文档 4、冲突处理 5、source中添加查询条件 6、source中包含多个源索引 7、限制处理的记录数 8、从远程ES集群中重建索引 9、提取随机子集 10、…

Spring Boot配置文件中的配置项加密jasypt使用

在Spring Boot中&#xff0c;有很多口令需要加密&#xff0c;如数据库连接密码、访问第三方接口的Token等。常见的方法就是用jasypt对口令进行加密。 实际上&#xff0c;jasypt可以对配置文件中任意配置项的值进行加密&#xff0c;不局限于对密码的加密。 1.在pom.xml中添加ja…