.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;}}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/35340.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/35340.shtml
英文地址,請注明出處:http://en.pswp.cn/news/35340.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

CCLINK轉MODBUS-TCP網關cclink通訊接線圖 終端電阻

大家好&#xff0c;今天我們要聊的是生產管理系統中的CCLINK和MODBUS-TCP協議&#xff0c;它們的不同使得數據互通比較困難&#xff0c;但捷米JM-CCLK-TCP網關的出現改變了這一切。 1捷米JM-CCLK-TCP是一款自主研發的CCLINK從站功能的通訊網關&#xff0c;它的主要功能是將各種…

后端開發5.Redis的搭建

使用docker安裝 Redis【redis】(6379) 拉取Redis鏡像 docker pull redis:6.2.6 啟動Redis容器 docker run -di --name=redis -p 6379:6379 redis:6.2.6 啟動Redis容器并設置密碼 docker run -di --name=redis -p 6379:6379 redis:6.2.6 --requirepass "密碼" 測…

D455+VINS-Fusion+surfelmapping 稠密建圖(三)

繼續&#xff0c;由surfelmapping建立的點云生成octomap八叉樹柵格地圖 一、安裝OctomapServer 建圖包 安裝插件 sudo apt-get install ros-melodic-octomap-ros sudo apt-get install ros-melodic-octomap-msgs sudo apt-get install ros-melodic-octomap-server sudo apt-…

cubemx hal stm32 舵機 可減速 任意位置停止 驅動代碼

CubeMX配置 對于 STM32 F407VE 這里的84是來自APB1那路2倍頻得到&#xff1a; 代碼部分 兩個舵機都是180度的 servo.c #include "servo.h" #include "tim.h" #include "stdio.h"__IO uint32_t g_SteerUWT[2] {0}; uint16_t g_SteerDeg[…

React Native Maps的使用

介紹 React Native Maps是一個用于在React Native應用中顯示地圖的庫。它提供了許多功能&#xff0c;如顯示地圖、標記位置、繪制多邊形等。以下是React Native Maps的使用步驟&#xff1a; 使用 首先&#xff0c;你需要在你的React Native項目中安裝React Native Maps庫。可…

青大數據結構【2014】

一、單選 二、簡答 為了解決順序隊列的假溢出問題&#xff0c;提出了循環隊列&#xff0c;即把存儲隊列的表從邏輯上看成一個環 判別隊列空和滿有三種方法&#xff1a; 1&#xff09;采用計數器判別&#xff0c;空時&#xff0c;計數器為0&#xff1b;滿時&#xff0c;計數器…

【設計模式——學習筆記】23種設計模式——中介者模式Mediator(原理講解+應用場景介紹+案例介紹+Java代碼實現)

文章目錄 案例引入案例一普通實現中介者模式 案例二 介紹基礎介紹登場角色尚硅谷 《圖解設計模式》 案例實現案例一&#xff1a;智能家庭類圖實現 案例二&#xff1a;登錄頁面邏輯實現說明類圖實現 總結文章說明 案例引入 案例一 普通實現 在租房過程中&#xff0c;客戶可能…

css 實現 html 元素內文字水平垂直居中的N種方法

上一篇博文寫了div 中元素居中的N種常用方法&#xff0c;那么單個html元素&#xff1a;div&#xff08;塊級元素代表&#xff09;&#xff0c;span&#xff08;行內元素代表&#xff09;中的文字如何水平垂直都居中呢&#xff1f;實現方法如下&#xff1a; 本文例子使用的 html…

WebAPIs 第二天

DOM事件基礎 事件監聽事件類型事件對象 一.事件監聽 ① 概念&#xff1a;就是讓程序檢測是否有事件發生&#xff0c;一旦有事件觸發&#xff0c;就立即調用一個函數做出響應&#xff0c;也成為綁定事件或者注冊事件 ② 語法&#xff1a;元素對象.addEventListener(事件類型&…

機器學習---對數幾率回歸

1. 邏輯回歸 邏輯回歸&#xff08;Logistic Regression&#xff09;的模型是一個非線性模型&#xff0c; sigmoid函數&#xff0c;又稱邏輯回歸函數。但是它本質上又是一個線性回歸模型&#xff0c;因為除去sigmoid映射函 數關系&#xff0c;其他的步驟&#xff0c;算法都是…

從零開始,貪吃蛇小游戲系列專欄完美收官!

&#x1f3ae; 從零開始&#xff0c;貪吃蛇小游戲系列專欄完美收官&#xff01; &#x1f40d; 各位游戲開發探索者們&#xff0c;大家好&#xff01;我是[億元程序員]&#xff0c;一位擁有8年游戲開發經驗的主程。經過一段時間的努力&#xff0c;我很高興地宣布&#xff0c;我…

阿里云預裝LAMP應用導致MySQL不顯示訪問密碼如何解決

&#x1f600;前言 本篇博文是關于阿里云云服務器ECS部署MySQL過程中出現的一下坑&#xff0c;希望能夠幫助到您&#x1f60a; &#x1f3e0;個人主頁&#xff1a;晨犀主頁 &#x1f9d1;個人簡介&#xff1a;大家好&#xff0c;我是晨犀&#xff0c;希望我的文章可以幫助到大家…

SUB-1G SOC芯片DP4306F 32 位 ARM Cortex-M0+內核替代CMT2380F32

DP4306F是一款高性能低功耗的單片集成收發機&#xff0c;集成MO核MCU&#xff0c;工作頻率可覆蓋200MHiz^ 1000MHz。 支持230/408/433/470/868/915頻段。該芯片集成了射頻接收器、射頻發射器、頻率綜合器、GFSK調制器、GFSK解調器等功能模塊。通過SPI接口可以對輸出功率、頻道選…

gitlab-Runner搭建

root wget https://packages.gitlab.com/runner/gitlab-runner/packages/fedora/29/gitlab-runner-12.6.0-1.x86_64.rpm/download.rpm rpm -ivh download.rpm ---- 安裝 rpm -Uvh download.rpm -----更新升級 然后運行&#xff1a; gitlab-runner register --url https://git…

RabbitMQ相關面試題

用到了哪些MQ?什么使用場景?MQ的組成部分?MQ宕機了怎么辦?如何進行持久化的? MQ的選型? Kafka:高吞吐量、低延遲的分布式消息隊列,主要用于大規模數據處理和流式處理 RocketMQ:RocketMQ是阿里巴巴開源的分布式消息隊列,具有高吞吐量、低延遲、高可靠性等特點 RabbitM…

【Go 基礎篇】Go語言浮點類型:探索浮點數的特點與應用

介紹 浮點數是計算機編程中用于表示實數的一種數據類型&#xff0c;用于處理具有小數部分的數值。Go語言&#xff08;Golang&#xff09;提供了兩種主要的浮點數類型&#xff1a;float32和float64&#xff0c;分別用于單精度和雙精度浮點數的表示。本篇博客將深入探討Go語言中…

38 | 浦發銀行股票分析案例

本文將通過一個浦發銀行股票分析案例,探討如何從多個維度對股票進行分析,包括基本面、技術面和市場環境等因素。我們將深入挖掘浦發銀行的財務數據、業務模式以及市場定位,以了解其內在價值和潛在風險。同時,我們還將考察技術面的指標,如價格走勢、均線形態等,以揭示市場…

linux 命令--常用關機命令

1.使用shutdown命令 shutdown命令是Linux系統下最常用的關機命令之一。它可以讓系統在指定時間內進行關機或者重啟操作。例如&#xff0c;下面的命令可以讓系統在5分鐘后進行關機操作&#xff1a; sudo shutdown -h5其中&#xff0c;“-h”表示關機&#xff0c;“5”表示5分鐘…

ThinkPHP8命名規范-ThinkPHP8知識詳解

本文主要講解thinkphp8的命名規范&#xff0c;主要包括&#xff1a;遵循PHP自身的PSR-2命名規范和PSR-4自動加載規范、目錄和文件命名規范、函數和類、屬性命名規范、常量和配置命名規范、數據表和字段命名規范、不能使用PHP保留字。 在使用thinkphp8開發項目之前&#xff0c;…

C#使用OpenCv(OpenCVSharp)圖像全局二值化處理實例

本文實例演示C#語言中如何使用OpenCv(OpenCVSharp)對圖像進行全局二值化處理。 目錄 圖像二值化原理 函數原型 參數說明 實例 效果 圖像二值化原理