在进行项目开发的时候,刚好需要用到对字符串表达式进行求值的处理场景,因此寻找了几个符合要求的第三方组件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#表达式,并通过赋值参数,实现动态表达式的求值处理。

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

  1. [转]页游开发中的 Python 组件与模式Presentation Transcript

    转: 页游开发中的 Python 组件与模式Presentation Transcript 1. 页游开发中的 Python 组件与模式 赖勇浩( http://laiyonghao.com ) 20 ...

  2. XCode和Cocoa在开发中使用第三方dylib示例

    XCode和Cocoa在开发中使用第三方dylib示例 www.educity.cn   发布者:yukowang   来源:网络转载   发布日期:2014年06月13日      XCode和Co ...

  3. vue中修改第三方组件的样式并不造成污染

    vue引用了第三方组件, 需要在组件中局部修改第三方组件的样式, 而又不想去除scoped属性造成组件之间的样式污染. 此时只能通过>>>,穿透scoped. 但是,在sass中存在 ...

  4. laravel中引入composer安装在vendor中的第三方组件

    一.安装第三方组件 方法一:使用命令行安装第三方(已phpword为例): composer require phpoffce/phpword ^v0..* 方法二: 修改主项目composer.js ...

  5. vue中修改第三方组件的样式不生效

    问题 在使用element-ui时,有时候想要修改组件内的样式,但不成功,例如 <div class="test"> <el-button>按钮</e ...

  6. Android应用开发中,第三方集成新浪微博(sinaWeiboSDK)的过程记录

    作为一个android开发人员,不可避免的要学会使用和集成第三方API的能力 而新浪微博作为现在最主要的新闻速递媒体,使用十分普遍,并且提供了较为详细的API接入方法,故此选择集成sinaWeibiS ...

  7. iPhone 和 iPad的ios 开发中 利用 WebViewJavascriptBridge组件,通过 UIWebView 对Html进行双向通讯

    本文转载至 http://blog.csdn.net/remote_roamer/article/details/7261490 WebViewJavascriptBridge 项目的 官网 http ...

  8. Android开发中导入第三方库所遇问题记录

    1.重复循环依赖的问题 (1)需求 如下图所示: 在Android 项目中,采用模块化开发,一个是主跑application--Mudule A,另外一个是library--Library B 1)M ...

  9. iOS开发中常用第三方库的使用和配置-GDataXML

    这篇文章旨在给自己以后需要时能及时的查到,省得每次都去baidu. 1. xml解析库-GDataXML 参考文章:http://blog.csdn.net/tangren03/article/det ...

随机推荐

  1. 时间篇之centos6下修复的ntp操作(ntpd和ntpdate两个服务区别)

    系统采样,本采样和命令都是在centos6.4的系统中进行 主要比较centos7和centos6之间的差异,因为大部分都开始采用centos7但是有些老系统还采用centos6,这样我们就需要熟悉c ...

  2. SpringMVC 解析(五)URI链接处理

    URI在网络请求中必不可少,Spring提供了一些工具类用于解析或者生成URL,比如根据参数生成GET的URL等.本文会对Spring MVC中的URI工具进行介绍,本文主要参考Spring官方文档. ...

  3. 2021.11.05 eleveni的水省选题的记录

    2021.11.05 eleveni的水省选题的记录 因为eleveni比较菜,但是eleveni不想写绿题(总不能说是被绿题虐得不想写),eleveni决定继续水noip原题. --实际上菜菜的el ...

  4. k8s入门之pod(四)

    pod是k8s项目中的最小编排单位,它是运行中的一组(一个或多个)容器,这些容器共享存储.网络.调度等资源,pod是一个逻辑概念,同一个名称空间下不同pod可以通过ip互相访问. 一.通过命令行方式管 ...

  5. Jquery_HTML-对HTML内容删除添加、操作CSS改变样式、遍历定位元素

    1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="U ...

  6. Java 获取Word中的所有插入和删除修订

    在 Word 文档中启用跟踪更改功能后,会记录文档中的所有编辑行为,例如插入.删除.替换和格式更改.对插入或删除的内容,可通过本文中介绍的方法来获取. 引入Jar 方法1 手动引入:将 Free Sp ...

  7. 这3个免费PPT素材网站,一定要收藏

    制作PPT,这三个网站的素材绝对够用! 1.象刀设计 https://www.101dao.com 象刀设计里面有非常多PPT模板,这个网站也是主打PPT素材. 分类很清晰,需要什么风格的素材能快速找 ...

  8. 论文解读(CGC)《CGC: Contrastive Graph Clustering for Community Detection and Tracking》

    论文信息 论文标题:CGC: Contrastive Graph Clustering for Community Detection and Tracking论文作者:Namyong Park, R ...

  9. C#中检查null的语法糖

    今天看到已经更新了devblogs,新增的C# 11的!!(用于检查null的语法)经过非常长的讨论,最后取消了.然后我又想起来null检查,这个可以说一说. 函数参数null检查 传统写法 写一个函 ...

  10. 通俗易懂的ArcGis开发快速入门

    前言 本文主要介绍ArcGis的ArcEngine开发,学习时,我们需要放下心里障碍,那就是Gis开发只是普通的软件开发,并不需要专业的GIS知识,就是非常普通的,调用相关的C++开发的COM组件. ...