我们创建了一个 School 对象,其中包含了教师列表和学生列表。现在,我们需要计算教师平均年龄和学生平均年龄。
(资料图)
//创建对象School school = new School(){ Name = "小菜学园", Teachers = new List() { new Teacher() {Name="波老师",Age=26}, new Teacher() {Name="仓老师",Age=28}, new Teacher() {Name="悠老师",Age=30}, }, Students= new List() { 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>( 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.CoreSystem.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)lambdaExpr.Compile();// 计算教师平均年龄var avgAge = func(teachers);
三、介绍System.Linq.Dynamic.Core使用此动态 LINQ 库,我们可以执行以下操作:
通过 LINQ 提供程序进行的基于字符串的动态查询。动态分析字符串以生成表达式树,例如ParseLambda和Parse方法。使用CreateType方法动态创建数据类。功能介绍普通的功能此处不赘述,如果感兴趣,可以从下文提供文档地址去寻找使用案例。
添加自定义方法类可以通过在静态帮助程序/实用工具类中定义一些其他逻辑来扩展动态 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 GetCustomTypes() => new[] { typeof(Utils)}.ToHashSet();}
文档地址源码地址:https://github.com/zzzprojects/System.Linq.Dynamic.Core文档地址:https://dynamic-linq.net/overview使用项目规则引擎RulesEngine中解析表达式的实现:https://github.com/microsoft/RulesEngine/wiki自己封装了低代码中公式编辑器中公式的解析功能四、浅析System.Linq.Dynamic.CoreSystem.Linq.Dynamic.Core中 DynamicExpressionParser 和 ExpressionParser 都是用于解析字符串表达式并生成 Lambda 表达式树的类,但它们之间有一些不同之处。
ExpressionParser 类支持解析任何合法的 C# 表达式,并生成对应的表达式树。这意味着您可以在表达式中使用各种运算符、方法调用、属性访问等特性。
DynamicExpressionParser 类则更加灵活和通用。它支持解析任何语言的表达式,包括动态语言和自定义 DSL(领域特定语言)
我们先看ExpressionParser这个类,它用于解析字符串表达式并生成 Lambda 表达式树。
我只抽取重要的和自己感兴趣的属性和方法。
TextParser 类,实现算法有点类似于有限状态自动机(FSM):https://leetcode.cn/problems/biao-shi-shu-zhi-de-zi-fu-chuan-lcof/solutions/372095/biao-shi-shu-zhi-de-zi-fu-chuan-by-leetcode-soluti/MethodFinder,使用了反射机制,通过调用 GetMethods() 方法获取指定类型中定义的所有方法,并根据参数数量和类型等条件检查参数是否符合特定的条件。如果参数满足了条件,则将该方法添加到结果列表中。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 _internals = new(); //用于存储字符串表达式中使用的符号和值。例如,如果表达式包含 @0 占位符,则可以使用 _symbols["@0"] 访问其值。 private readonly Dictionary _symbols; //表示外部传入的参数和变量。如果表达式需要引用外部的参数或变量,则应该将它们添加到 _externals 中。 private IDictionary? _externals; /// /// 使用TextParser将字符串解析为指定的结果类型. /// /// /// 是否创建带有相同名称的构造函数 /// Expression 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; }}
Copyright @ 2015-2022 中国IT时代网版权所有 备案号: 沪ICP备2022005074号-4 联系邮箱:58 55 97 3@qq.com