一、引子·功能需求

我们创建了一个 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 类的文档地址为::https://learn.microsoft.com/zh-cn/dotnet/api/system.linq.expressions.expression?view=net-6.0

// 创建参数表达式
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();
}

文档地址

使用项目

四、浅析System.Linq.Dynamic.Core

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

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

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

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

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

public class ExpressionParser
{
//字符串解析器的配置,比如区分大小写、是否自动解析类型、自定义类型解析器等
private readonly ParsingConfig _parsingConfig; //查找指定类型中的方法信息,通过反射获取MethodInfo
private 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;
} // ?: operator
private Expression ParseConditionalOperator()
{
int errorPos = _textParser.CurrentToken.Pos;
Expression expr = ParseNullCoalescingOperator();
if (_textParser.CurrentToken.Id == TokenId.Question)
{
......
}
return expr;
} // ?? (null-coalescing) operator
private Expression ParseNullCoalescingOperator()
{
Expression expr = ParseLambdaOperator();
......
return expr;
}
// => operator - Added Support for projection operator
private Expression ParseLambdaOperator()
{
Expression expr = ParseOrOperator();
......
return expr;
} }

.NET实现解析字符串表达式的更多相关文章

  1. 在C#开发中使用第三方组件LambdaParser、DynamicExpresso、Z.Expressions,实现动态解析/求值字符串表达式

    在进行项目开发的时候,刚好需要用到对字符串表达式进行求值的处理场景,因此寻找了几个符合要求的第三方组件LambdaParser.DynamicExpresso.Z.Expressions,它们各自功能 ...

  2. 解决JSP 不解析EL表达式

    解决JSP 不解析EL表达式,jsp在使用EL表达式的时候发现它不被解析,而是直接以字符串的形式显示了出来,经过查阅资料和实践,终于得知了原因并找到了解决方案 原因是:在默认情况下,Servlet 2 ...

  3. 解析数学表达式 代码解析AST语法树

    2019年2月20日09:18:22 AST语法树自己写代码解析的话就比较麻烦,有现成的库可以解析PHP,就像webpack就是自己解析js的语法代码,编译成各种版本的可用代码 github http ...

  4. PHP 实现字符串表达式计算

    什么是字符串表达式?即,将我们常见的表达式文本写到了字符串中,如:"$age >= 20",$age 的值是动态的整型变量. 什么是字符串表达式计算?即,我们需要一段程序来执 ...

  5. NVelocity解析字符串

    之前都是先从模板文件里面读取html字符串,现在要求将模板存入数据库或缓存了,怎么办呢?在网上找了下资料,终于找到解决办法. 如下: public class NVelocityHelper { // ...

  6. 字符串表达式String Expressions

    声明:原创作品,转载时请注明文章来自SAP师太技术博客( 博/客/园www.cnblogs.com):www.cnblogs.com/jiangzhengjun,并以超链接形式标明文章原始出处,否则将 ...

  7. [SAP ABAP开发技术总结]字符串表达式String Expressions

    声明:原创作品,转载时请注明文章来自SAP师太技术博客( 博/客/园www.cnblogs.com):www.cnblogs.com/jiangzhengjun,并以超链接形式标明文章原始出处,否则将 ...

  8. sql 解析字符串添加到临时表中 sql存储过程in 参数输入

    sql 解析字符串添加到临时表中  sql存储过程in 参数输入 解决方法 把字符串解析 添加到 临时表中 SELECT * into #临时表   FROM dbo.Func_SplitOneCol ...

  9. 使用堆栈结构进行字符串表达式("7*2-5*3-3+6/3")的计算

    问题: 给定字符串String str = "7*2-5*3-3+6/3", 求出字符串里面表达式的结果? 像javascript有自带的eval()方法,可以直接计算.但java ...

  10. CF552E 字符串 表达式求值

    http://codeforces.com/contest/552/problem/E E. Vanya and Brackets time limit per test 1 second memor ...

随机推荐

  1. SQL语句底层执行顺序

    1. SELECT 语句的完整结构 SQL92语法: SELECT ...,....,(存在聚合函数) FROM ...,...,... WHERE 多表的连接条件 AND 不包含聚合函数的过滤条件 ...

  2. jjencode|JS混淆手工解析编码过程以及自定义混淆变体改装

    此前发布过关于aaencode的混淆编码,此篇文章继续说同作者的jjencode混淆编码的具体过程. 介绍 首先是jjencode的作者提供的编码测试页:http://utf-8.jp/public/ ...

  3. JSON反序列化接口的问题

    今天在使用JSON序列化类时出现问题,原来类中有一个接口,在反序列化时不知道接口的实体是什么 public class Device : IComparer {         private str ...

  4. Caused by: java.lang.NoClassDefFoundError: net/minidev/asm/FieldFilter 报错的解决

    Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'r ...

  5. unity更改c#文件名的小tip

    今天偶然知道了一个在Unity中更改代码文件名的小技巧--最好先在Unity的project视图里找到文件,改完后再去visual studio等代码编辑器里改里面的类名. 以前都没注意,想起来要改某 ...

  6. NX 二次开发 多个功能集成一个DLL的方法

    用C++做NX二次开发,一个功能建一个工程,管理起来很不方便,在网上找了些资料学习,成功的把多个功能做到一个工程里(多功能集成到一个DLL). 1.首先要了解外挂的菜单文件的基础,要实现多个功能集合在 ...

  7. arcengine动态显示所需字段值

    需求:实现和GIS桌面端中Identify的类似功能,鼠标滑动的时候可以显示鼠标所在位置的要素的指定字段的值.. 主要操作流程: ①先打开一个对话框,用于选择需要显示的图层和字段名 ②点击确定之后,在 ...

  8. Go_day01

    Go基础语法 注释 注释是为了增强代码的可读性,不会参与程序的一切功能, go语言注释分为单行注释与多行注释 单行注释 //双斜杠 每行都要添加 // 多行注释 /* */ 一次标记多行注释输入 pa ...

  9. typescript开发vue项目二次封装的axios用return Promise.reject(error) 返回异常,提示[Vue warn]: Error in v-on handler (Promise/async)

    二次封装axios时刻意服务端模拟了延迟返回数据的场景,用return Promise.reject(error) 返回异常,报如下错误, [Vue warn]: Error in v-on hand ...

  10. python selenium 操作文件上传,并发操作时,文件选择窗口混乱解决方案

    上传文件 使用的是 python + autoit 模块,这种方式有一个问题,当出现多条任务同时选择文件上传的时候,无法判断那个文件选择窗口的归属,从而出现上传了错误的文件! 解决方法: 要上载文件而 ...