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

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 在C#开发中使用第三方组件LambdaParser、DynamicExpresso、Z.Expressions,实现动态解析/求值字符串表达式

在进行项目开发的时候,刚好需要用到对字符串表达式进行求值的处理场景,因此寻找了几个符合要求的第三方组件LambdaParser、DynamicExpresso、Z.Expressions,它们各自功能有所不同,不过基本上都能满足要求。它们都可以根据相关的参数进行字符串表达式的求值,本篇随笔介绍它们三者的使用代码,以及总结其中的一些经验。

数学表达式求值应该是最常见的,一般我们在应用程序中如果需要计算,是需要对参数进行类型转换,然后在后台进行相应计算的。但是如果是计算一些符合的式子或者公式,特别是参数不一定的情况下,这个就比较麻烦。利用第三方组件,对表达式进行快速求值,可以满足我们很多实际项目上的需求,而且处理起来也很方便。

这几个第三方组件,它们的GitHub或官网地址:

https://github.com/nreco/lambdaparser

https://github.com/dynamicexpresso/DynamicExpresso

https://eval-expression.net/eval-execute

不过Z.Expressions是收费的,前两者都是免费的。

 

我使用字符串表达式进行求值的场景,主要就是想对一个SQL条件的表达式,转换为普通的字符串表达式,然后根据对象的参数值,进行求值处理,这几个表达式求值组件都支持这样的操作,为了更好演示它们的使用效果及代码,我们专门创建了一个案例代码进行测试验证,确认满足我的实际需求。

 

1、Z.Expressions.Eval 表达式解析

Z.Expression.Eval是一个免费开源的(后续收费了),可扩展的,超轻量级的公式化语言解析执行工具包,可以在运行时解析C#表达式的开源免费组件。Z.Expressions从2.0开始支持了NetCore,但是收费的。参考地址:https://riptutorial.com/eval-expression/learn/100000/getting-started 或者 https://eval-expression.net/eval-execute

在运行时解析C#表达式,例如一些工资或者成本核算系统,就需要在后台动态配置计算表达式,从而进行计算求值。

下面对几个不同的案例代码进行介绍及输出结果验证

匿名类型处理

//匿名类型
string expression = "a*2 + b*3 - 3";
int result = Eval.Execute<int>(expression, new { a = 10, b = 5 });
Console.WriteLine("{0} = {1}", expression, result); //a*2 + b*3 - 3 = 32

指定参数

//指定参数
expression = "{0}*2 + {1}*3 - 3";
result = Eval.Execute<int>(expression, 10, 5);
Console.WriteLine("{0} = {1}", expression, result);//{0}*2 + {1}*3 - 3 = 32

类对象

//类对象
expression = "a*2 + b*3 - 3";
dynamic expandoObject = new ExpandoObject();
expandoObject.a = 10;
expandoObject.b = 5;
result = Eval.Execute<int>(expression, expandoObject);
Console.WriteLine("{0} = {1}", expression, result); //a*2 + b*3 - 3 = 32

字典对象

//字典对象
expression = "a*2 + b*3 - 3";
var values = new Dictionary<string, object>()
{
    { "a", 10 },
    { "b", 5 }
};
result = Eval.Execute<int>(expression, values);
Console.WriteLine("{0} = {1}", expression, result);//a*2 + b*3 - 3 = 32

委托类型

//委托类型1
expression = "{0}*2 + {1}*3";
var compiled = Eval.Compile<Func<int, int, int>>(expression);
result = compiled(10, 15);
Console.WriteLine("{0} = {1}", expression, result);//{0}*2 + {1}*3 = 65
//委托类型2
expression = "a*2 + b*3";
compiled = Eval.Compile<Func<int, int, int>>(expression, "a", "b");
result = compiled(10, 15);
Console.WriteLine("{0} = {1}", expression, result);//a*2 + b*3 = 65

字符串扩展支持

//字符串扩展支持-匿名类型
expression = "a*2 + b*3 - 3";
result = expression.Execute<int>(new { a = 10, b = 5 });
Console.WriteLine("{0} = {1}", expression, result);//a*2 + b*3 - 3 = 32
//字符串扩展支持-字典类型
expression = "a*2 + b*3 - 3";
values = new Dictionary<string, object>()
{
    { "a", 10 },
    { "b", 5 }
};
result = expression.Execute<int>(values);
Console.WriteLine("{0} = {1}", expression, result);//a*2 + b*3 - 3 = 32

可以看出,该组件提供了非常丰富的表达式运算求值处理方式。

 

2、NReco.LambdaParser 表达式解析

我看中这个组件的处理,主要是因为它能够传入参数是字典类型,这样我可以非常方便的传入各种类型的参数,并且这个组件比较接近SQL语法,可以设置利用常规的=代替表达式的==,这样对于SQL语句来说是方便的。

它的案例代码如下所示。

/// <summary>
/// NReco.LambdaParser 表达式解析
/// </summary>
private void btnLamdaParser_Click(object sender, EventArgs e)
{
    var lambdaParser = new NReco.Linq.LambdaParser();
    var dict = new Dictionary<string, object>();
    dict["pi"] = 3.14M;
    dict["one"] = 1M;
    dict["two"] = 2M;
    dict["test"] = "test";
    Console.WriteLine(lambdaParser.Eval("pi>one && 0<one ? (1+8)/3+1*two : 0", dict)); // --> 5
    Console.WriteLine(lambdaParser.Eval("test.ToUpper()", dict)); // --> TEST
    Console.WriteLine(lambdaParser.Eval("pi>one && 0<one ", dict)); // --> True
    Console.WriteLine(lambdaParser.Eval("test.ToUpper()", dict)); // --> TEST
}

同样它支持的算术符号操作有:+, -, *, /, %,以及常规的逻辑判断:==, !=, >, <, >=, <=,如果需要它允许把=作为==比较,那么设置属性 AllowSingleEqualSign  = true 即可,如下代码。

var lambdaParser = new LambdaParser();
    lambdaParser.AllowSingleEqualSign = true;//可以使用 = 作为逻辑判断,如Title ="Leader",而不用Title =="Leader"
    var evalResult = lambdaParser.Eval(repalce, dict);

该组件没有过多提供例子,不过它的例子提供的关键点,基本上都能实现我们实际的表达式求值处理要求了。

 

3、DynamicExpresso 表达式解析

相对于LambdaParser的简洁、Z.Expressions收费处理,Dynamic Expresso 可以说是提供了一个非常强大的、免费开源的处理类库,它提供非常多的表达式求值的实现方式。

简单的字符串表达式求值如下代码

var interpreter = new Interpreter();
var result = interpreter.Eval("8 / 2 + 2");

但是一般我们需要传入一定的参数进行表达式求值的。

var target = new Interpreter();
double result = target.Eval<double>("Math.Pow(x, y) + 5",
     new Parameter("x", typeof(double), 10),
     new Parameter("y", typeof(double), 2));

或者

var interpreter = new Interpreter();
var parameters = new[] {
    new Parameter("x", 23),
    new Parameter("y", 7)
};
Assert.AreEqual(30, interpreter.Eval("x + y", parameters));

或者赋值指定的参数

var target = new Interpreter().SetVariable("myVar", 23);
Assert.AreEqual(23, target.Eval("myVar"));

对于字典类型的处理,是我喜欢的方式,它的案例代码如下所示。

var interpreter = new Interpreter();
var dict = new Dictionary<string, object>();
dict.Add("a", 1.0);
dict.Add("b", 2);
dict.Add("d", 4);
dict.Add("e", 5);
dict.Add("str", 'f');
foreach (var v in dict)
{
    object value = v.Value;
    int para = 0;
    if (int.TryParse(v.Value.ToString(), out para))
    {
        value = (float)para;
    }
    interpreter.SetVariable(v.Key, value);
}
Console.WriteLine(interpreter.Eval("a+b").ToString()); //3
Console.WriteLine(interpreter.Eval("a/b").ToString()); //0.5
Console.WriteLine(interpreter.Eval("a > b").ToString()); //False
Console.WriteLine(interpreter.Eval("str == 'f'").ToString()); //True

对于类的属性表达式查询,测试代码如下所示

var customers = new List<Customer> {
        new Customer() { Name = "David", Age = 31, Gender = 'M' },
        new Customer() { Name = "Mary", Age = 29, Gender = 'F' },
        new Customer() { Name = "Jack", Age = 2, Gender = 'M' },
        new Customer() { Name = "Marta", Age = 1, Gender = 'F' },
        new Customer() { Name = "Moses", Age = 120, Gender = 'M' },
    };
    string whereExpression = "customer.Age > 18 && customer.Gender == 'F'";
    Func<Customer, bool> dynamicWhere = interpreter.ParseAsDelegate<Func<Customer, bool>>(whereExpression, "customer");
    Console.WriteLine(customers.Where(dynamicWhere).Count());//=> 1
    var customer_query = (new List<Customer> {
        new Customer() { Name = "David", Age = 31, Gender = 'M' },
        new Customer() { Name = "Mary", Age = 29, Gender = 'F' },
        new Customer() { Name = "Jack", Age = 2, Gender = 'M' },
        new Customer() { Name = "Marta", Age = 1, Gender = 'F' },
        new Customer() { Name = "Moses", Age = 120, Gender = 'M' },
    }).AsQueryable();
    whereExpression = "customer.Age > 18 && customer.Gender == 'F'";
    var expression = interpreter.ParseAsExpression<Func<Customer, bool>>(whereExpression, "customer");
    Console.WriteLine(customer_query.Where(expression).Count());//=> 1

 

4、SQL条件语句的正则表达式和字符串求值处理

前面介绍了几个表达式求值处理的组件,他们基本上都能够满足实际的求值处理,只是提供的功能有所侧重。

我主要希望用它来对特定的表达式进行求布尔值,判断表达式是否满足条件的。

例如对于sql条件语句:(Amount> 500 and Title ='Leader') or Age> 32, 以及一个字典对象的参数集合,我希望能够提取里面的Amount、Title、Leader、Age这样的键,然后给字典赋值,从而判断表达式的值。

由于sql表达式和C#代码的表达式逻辑语法有所差异,我们需要替换and Or 为实际的&& || 字符,因此给定替换的正则表达式:\sand|\sor

而我需要先提取条件语句的键值内容,然后获得指定的键参数,那么也要提供一个正则表达式:\w*[^>=<!'()\s] ,这个正则表达式主要就是提取特定的字符匹配。

提取内容的C#代码逻辑如下所示。

private void btnRegexExtract_Click(object sender, EventArgs e)
        {
            var source = this.txtSource.Text;
            //先替换部分内容 \sand|\sor
            source = Regex.Replace(source, this.txtReplaceRegex.Text, "");//替换表达式
            //增加一行记录主内容
            this.txtContent.Text += "替换正则表达式后内容:";
            this.txtContent.AppendText(Environment.NewLine);
            this.txtContent.Text += source;
            this.txtContent.AppendText(Environment.NewLine);
            //在匹配内容处理
            var regex = new Regex(this.txtRegex.Text);
            var matches = regex.Matches(source);
            //遍历获得每个匹配的内容
            var fieldList = new List<string>();
            int i = 0;
            foreach (Match match in matches)
            {
                this.txtContent.AppendText(match.Value);
                this.txtContent.AppendText(Environment.NewLine);
                if (i++ % 2 == 0)
                {
                    fieldList.Add(match.Value);
                }
            }
            this.txtContent.AppendText("获得表达式键:");
            this.txtContent.AppendText(Environment.NewLine);
            this.txtContent.AppendText(fieldList.ToJson());
            this.txtContent.AppendText(Environment.NewLine);
            var repalce = ReplaceExpress(this.txtSource.Text);
            this.txtContent.AppendText("替换And=>&& or=>|| '=> \" 操作符后内容:");
            this.txtContent.AppendText(Environment.NewLine);
            this.txtContent.AppendText(repalce);
        }
/// <summary>
        /// 替换And=>&& or=>|| '=> \" 操作符后内容
        /// </summary>
        /// <param name="source"></param>
        /// <returns></returns>
        private string ReplaceExpress(string source)
        {
            //操作符替换表达式
            var repalce = Regex.Replace(source, @"\sand\s", " && "); //and => &&
            repalce = Regex.Replace(repalce, @"\sor\s", " || "); //or => ||
            repalce = Regex.Replace(repalce, @"'", "\""); //'=> \"
            return repalce;
        }

表达式处理结果如下所示

它的逻辑代码如下。

private void btnRunExpression_Click(object sender, EventArgs e)
        {
            //操作符替换表达式
            var repalce = ReplaceExpress(this.txtSource.Text);
            this.txtContent.Text = "替换And=>&& or=>|| '=> \" 操作符后内容:";
            this.txtContent.AppendText(Environment.NewLine);
            this.txtContent.Text += repalce;
            this.txtContent.AppendText(Environment.NewLine);
            this.txtContent.AppendText(Environment.NewLine);
            //(Amount> 500 and Title ='Leader') or Age> 32
            var dict = new Dictionary<string, object>();
            dict["Amount"] = 600;
            dict["Title"] = "Leader";
            dict["Age"] = 40;
            
            this.txtContent.AppendText("字典内容");
            foreach(var key in dict.Keys)
            {
                this.txtContent.AppendText($"{key}:{dict[key]} ");
            }
            this.txtContent.AppendText(Environment.NewLine);
            this.txtContent.AppendText(Environment.NewLine);
            //var valComparer = new ValueComparer() { NullComparison = ValueComparer.NullComparisonMode.Sql };
            //var lambdaParser = new LambdaParser(valComparer);
            var lambdaParser = new LambdaParser();
            lambdaParser.AllowSingleEqualSign = true;//可以使用=作为判断,如Title ="Leader",而不用Title =="Leader"
            var express1 = "(Amount> 500 && Title = \"Leader\") or Age>30";
            var result1 = lambdaParser.Eval(express1, dict);
            this.txtContent.AppendText("LambdaParser 表达式处理:");
            this.txtContent.AppendText(Environment.NewLine);
            this.txtContent.AppendText(express1 + " => " + result1);
            var express2 = "( Amount> 500 && Title =\"leader\" )"; //字符串比较(''=> "")
            var result2 = lambdaParser.Eval(express2, dict);
            this.txtContent.AppendText(Environment.NewLine);
            this.txtContent.AppendText(express2 + " => " + result2);
            var express3 = "Amount> 500";
            var result3 = lambdaParser.Eval(express3, dict);
            this.txtContent.AppendText(Environment.NewLine);
            this.txtContent.AppendText(express3 + " => " + result3);
            var express4 = "Title = \"Leader\" "; //字符串比较(''=> "")
            var result4 = lambdaParser.Eval(express4, dict);
            this.txtContent.AppendText(Environment.NewLine);
            this.txtContent.AppendText(express4 + " => " + result4);
            this.txtContent.AppendText(Environment.NewLine);
            Console.WriteLine(lambdaParser.Eval("Title.ToString()", dict)); // --> Leader
            //DynamicExpresso 表达式解析处理
            this.txtContent.AppendText(Environment.NewLine);
            this.txtContent.AppendText(Environment.NewLine);
            this.txtContent.AppendText("DynamicExpresso 表达式解析处理:");
            var interpreter = new Interpreter();
            foreach (var v in dict)
            {
                interpreter.SetVariable(v.Key, v.Value);
            }
            //express3 = "Amount> 500";
            var result33 = interpreter.Eval(express3);
            this.txtContent.AppendText(Environment.NewLine);
            this.txtContent.AppendText(express3 + " => " + result33);
            //使用''出错,字符串比较需要使用""
            try
            {
                express4 = "Title == \"Leader\" ";
                var result44 = interpreter.Eval(express4);
                this.txtContent.AppendText(Environment.NewLine);
                this.txtContent.AppendText(express4 + " => " + result44);
            }
            catch(Exception ex)
            {
                this.txtContent.AppendText(Environment.NewLine);
                this.txtContent.AppendText(express4 + ",解析出错 => " + ex.Message);
            }
            //var dict = new Dictionary<string, object>();
            //dict["Amount"] = 600;
            //dict["Title"] = "Leader";
            //dict["Age"] = 40;
            this.txtContent.AppendText(Environment.NewLine);
            this.txtContent.AppendText(Environment.NewLine);
            this.txtContent.AppendText("Z.Expressions.Eval 表达式解析:");
            var result333 = express3.Execute<bool>(dict);
            this.txtContent.AppendText(Environment.NewLine);
            this.txtContent.AppendText(express3 + " => " + result333);
            express4 = "Title == 'Leader'"; //Z.Expressions可以接受 ' 代替 "
            var result444 = express4.Execute<bool>(dict);
            this.txtContent.AppendText(Environment.NewLine);
            this.txtContent.AppendText(express4 + " => " + result444);
        }

这样我们就可以转换SQL条件表达式为实际的C#表达式,并通过赋值参数,实现动态表达式的求值处理。

 

专注于代码生成工具、.Net/.NetCore 框架架构及软件开发,以及各种Vue.js的前端技术应用。著有Winform开发框架/混合式开发框架、微信开发框架、Bootstrap开发框架、ABP开发框架、SqlSugar开发框架等框架产品。
 转载请注明出处:撰写人:伍华聪  http://www.iqidi.com

相关文章
|
18天前
|
前端开发 JavaScript 安全
C#一分钟浅谈:Blazor WebAssembly 开发
Blazor WebAssembly 是一个客户端框架,允许开发者使用C#和Razor语法构建Web应用。本文介绍了Blazor WebAssembly的基本概念、常见问题及解决方案,包括路由配置、数据绑定、异步操作、状态管理和性能优化等方面的内容,并分享了一些易错点及如何避免的方法。希望这些内容能帮助你在Blazor WebAssembly开发中少走弯路,提高开发效率。
88 51
|
1天前
|
Java 调度 Android开发
安卓与iOS开发中的线程管理差异解析
在移动应用开发的广阔天地中,安卓和iOS两大平台各自拥有独特的魅力。如同东西方文化的差异,它们在处理多线程任务时也展现出不同的哲学。本文将带你穿梭于这两个平台之间,比较它们在线程管理上的核心理念、实现方式及性能考量,助你成为跨平台的编程高手。
|
16天前
|
开发框架 缓存 .NET
C# 一分钟浅谈:Blazor Server 端开发
Blazor Server 是基于 ASP.NET Core 的框架,允许使用 C# 和 Razor 语法构建交互式 Web 应用。本文介绍 Blazor Server 的基本概念、快速入门、常见问题及解决方案,帮助开发者快速上手。涵盖创建应用、基本组件、数据绑定、状态管理、跨组件通信、错误处理和性能优化等内容。
29 1
|
17天前
|
缓存 C# 开发者
C# 一分钟浅谈:Blazor Server 端开发
本文介绍了 Blazor Server,一种基于 .NET 的 Web 开发模型,允许使用 C# 和 Razor 语法构建交互式 Web 应用。文章从基础概念、创建应用、常见问题及解决方案、易错点及避免方法等方面详细讲解,帮助开发者快速上手并提高开发效率。
40 2
|
26天前
|
测试技术 Go C#
C#一分钟浅谈:ReSharper 插件增强开发效率
【10月更文挑战第25天】ReSharper 是 JetBrains 开发的一款 Visual Studio 插件,旨在提高 .NET 开发者的生产力。它通过代码分析、重构、导航等功能,帮助开发者避免常见错误,提升代码质量和开发效率。本文将通过具体代码案例,详细介绍 ReSharper 的常见功能及其应用。
38 1
|
1月前
|
监控 安全 Serverless
"揭秘D2终端大会热点技术:Serverless架构最佳实践全解析,让你的开发效率翻倍,迈向技术新高峰!"
【10月更文挑战第23天】D2终端大会汇聚了众多前沿技术,其中Serverless架构备受瞩目。它让开发者无需关注服务器管理,专注于业务逻辑,提高开发效率。本文介绍了选择合适平台、设计合理函数架构、优化性能及安全监控的最佳实践,助力开发者充分挖掘Serverless潜力,推动技术发展。
58 1
|
1月前
|
前端开发 JavaScript 开发者
揭秘前端高手的秘密武器:深度解析递归组件与动态组件的奥妙,让你代码效率翻倍!
【10月更文挑战第23天】在Web开发中,组件化已成为主流。本文深入探讨了递归组件与动态组件的概念、应用及实现方式。递归组件通过在组件内部调用自身,适用于处理层级结构数据,如菜单和树形控件。动态组件则根据数据变化动态切换组件显示,适用于不同业务逻辑下的组件展示。通过示例,展示了这两种组件的实现方法及其在实际开发中的应用价值。
34 1
|
1月前
|
C# Python
使用wxpython开发跨平台桌面应用,对wxpython控件实现类似C#扩展函数处理的探究
【10月更文挑战第30天】使用 `wxPython` 开发跨平台桌面应用时,可以通过创建辅助类来模拟 C# 扩展函数的功能。具体步骤包括:1. 创建辅助类 `WxWidgetHelpers`;2. 在该类中定义静态方法,如 `set_button_color`;3. 在应用中调用这些方法。这种方法提高了代码的可读性和可维护性,无需修改 `wxPython` 库即可为控件添加自定义功能。但需要注意显式调用方法和避免命名冲突。
|
21天前
|
开发工具 Android开发 数据安全/隐私保护
探索移动应用的世界:从开发到操作系统的全面解析
【10月更文挑战第33天】在数字化时代,移动应用已成为我们日常生活中不可或缺的一部分。本文将深入探讨移动应用的开发过程,包括编程语言、开发工具和框架的选择,以及如何构建用户友好的界面。同时,我们还将分析移动操作系统的核心功能和安全性,以帮助读者更好地理解这些应用程序是如何在各种设备上运行的。无论你是开发者还是普通用户,这篇文章都将为你揭示移动应用背后的奥秘。
|
26天前
|
机器学习/深度学习 自然语言处理 数据管理
GraphRAG核心组件解析:图结构与检索增强生成
【10月更文挑战第28天】在当今数据科学领域,自然语言处理(NLP)和图数据管理技术的发展日新月异。GraphRAG(Graph Retrieval-Augmented Generation)作为一种结合了图结构和检索增强生成的创新方法,已经在多个应用场景中展现出巨大的潜力。作为一名数据科学家,我对GraphRAG的核心组件进行了深入研究,并在此分享我的理解和实践经验。
50 0