C#使用第三方組件實(shí)現(xiàn)動(dòng)態(tài)解析和求值字符串表達(dá)式
介紹
在進(jìn)行項(xiàng)目開發(fā)的時(shí)候,剛好需要用到對(duì)字符串表達(dá)式進(jìn)行求值的處理場(chǎng)景,因此尋找了幾個(gè)符合要求的第三方組件LambdaParser、DynamicExpresso、Z.Expressions,它們各自功能有所不同,不過基本上都能滿足要求。它們都可以根據(jù)相關(guān)的參數(shù)進(jìn)行字符串表達(dá)式的求值,本篇隨筆介紹它們?nèi)叩氖褂么a,以及總結(jié)其中的一些經(jīng)驗(yàn)。
數(shù)學(xué)表達(dá)式求值應(yīng)該是最常見的,一般我們?cè)趹?yīng)用程序中如果需要計(jì)算,是需要對(duì)參數(shù)進(jìn)行類型轉(zhuǎn)換,然后在后臺(tái)進(jìn)行相應(yīng)計(jì)算的。但是如果是計(jì)算一些符合的式子或者公式,特別是參數(shù)不一定的情況下,這個(gè)就比較麻煩。利用第三方組件,對(duì)表達(dá)式進(jìn)行快速求值,可以滿足我們很多實(shí)際項(xiàng)目上的需求,而且處理起來也很方便。
這幾個(gè)第三方組件,它們的GitHub或官網(wǎng)地址:
https://github.com/nreco/lambdaparser
https://github.com/dynamicexpresso/DynamicExpresso
https://eval-expression.net/eval-execute
不過Z.Expressions是收費(fèi)的,前兩者都是免費(fèi)的。
我使用字符串表達(dá)式進(jìn)行求值的場(chǎng)景,主要就是想對(duì)一個(gè)SQL條件的表達(dá)式,轉(zhuǎn)換為普通的字符串表達(dá)式,然后根據(jù)對(duì)象的參數(shù)值,進(jìn)行求值處理,這幾個(gè)表達(dá)式求值組件都支持這樣的操作,為了更好演示它們的使用效果及代碼,我們專門創(chuàng)建了一個(gè)案例代碼進(jìn)行測(cè)試驗(yàn)證,確認(rèn)滿足我的實(shí)際需求。

1、Z.Expressions.Eval 表達(dá)式解析
Z.Expression.Eval是一個(gè)免費(fèi)開源的(后續(xù)收費(fèi)了),可擴(kuò)展的,超輕量級(jí)的公式化語言解析執(zhí)行工具包,可以在運(yùn)行時(shí)解析C#表達(dá)式的開源免費(fèi)組件。Z.Expressions從2.0開始支持了NetCore,但是收費(fèi)的。參考地址:https://riptutorial.com/eval-expression/learn/100000/getting-started 或者 https://eval-expression.net/eval-execute。
在運(yùn)行時(shí)解析C#表達(dá)式,例如一些工資或者成本核算系統(tǒng),就需要在后臺(tái)動(dòng)態(tài)配置計(jì)算表達(dá)式,從而進(jìn)行計(jì)算求值。
下面對(duì)幾個(gè)不同的案例代碼進(jìn)行介紹及輸出結(jié)果驗(yàn)證
匿名類型處理
//匿名類型
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指定參數(shù)
//指定參數(shù)
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類對(duì)象
//類對(duì)象
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字典對(duì)象
//字典對(duì)象
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字符串?dāng)U展支持
//字符串?dāng)U展支持-匿名類型
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
//字符串?dāng)U展支持-字典類型
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可以看出,該組件提供了非常豐富的表達(dá)式運(yùn)算求值處理方式。
2、NReco.LambdaParser 表達(dá)式解析
我看中這個(gè)組件的處理,主要是因?yàn)樗軌騻魅雲(yún)?shù)是字典類型,這樣我可以非常方便的傳入各種類型的參數(shù),并且這個(gè)組件比較接近SQL語法,可以設(shè)置利用常規(guī)的=代替表達(dá)式的==,這樣對(duì)于SQL語句來說是方便的。
它的案例代碼如下所示。
/// <summary>
/// NReco.LambdaParser 表達(dá)式解析
/// </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
}同樣它支持的算術(shù)符號(hào)操作有:+, -, *, /, %,以及常規(guī)的邏輯判斷:==, !=, >, <, >=, <=,如果需要它允許把=作為==比較,那么設(shè)置屬性 AllowSingleEqualSign = true 即可,如下代碼。
var lambdaParser = new LambdaParser();
lambdaParser.AllowSingleEqualSign = true;//可以使用 = 作為邏輯判斷,如Title ="Leader",而不用Title =="Leader"
var evalResult = lambdaParser.Eval(repalce, dict);該組件沒有過多提供例子,不過它的例子提供的關(guān)鍵點(diǎn),基本上都能實(shí)現(xiàn)我們實(shí)際的表達(dá)式求值處理要求了。
3、DynamicExpresso 表達(dá)式解析
相對(duì)于LambdaParser的簡(jiǎn)潔、Z.Expressions收費(fèi)處理,Dynamic Expresso 可以說是提供了一個(gè)非常強(qiáng)大的、免費(fèi)開源的處理類庫(kù),它提供非常多的表達(dá)式求值的實(shí)現(xiàn)方式。
簡(jiǎn)單的字符串表達(dá)式求值如下代碼
var interpreter = new<strong> Interpreter</strong>();
var result = interpreter.Eval("8 / 2 + 2");但是一般我們需要傳入一定的參數(shù)進(jìn)行表達(dá)式求值的。
var target = new<strong> Interpreter</strong>();
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<strong> Interpreter</strong>();
var parameters = new[] {
new Parameter("x", 23),
new Parameter("y", 7)
};
Assert.AreEqual(30, interpreter.Eval("x + y", parameters));或者賦值指定的參數(shù)
var target = new Interpreter().SetVariable("myVar", 23);
Assert.AreEqual(23, target.Eval("myVar"));對(duì)于字典類型的處理,是我喜歡的方式,它的案例代碼如下所示。
var interpreter = new<strong> Interpreter</strong>();
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對(duì)于類的屬性表達(dá)式查詢,測(cè)試代碼如下所示
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 = "<strong>customer.Age > 18 && customer.Gender == 'F'</strong>";
Func<Customer, bool> dynamicWhere = interpreter.ParseAsDelegate<Func<Customer, bool>>(whereExpression, "<strong>customer</strong>");
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 = "<strong>customer.Age > 18 && customer.Gender == 'F'</strong>";
var expression = interpreter.ParseAsExpression<Func<Customer, bool>>(whereExpression, "<strong>customer</strong>");
Console.WriteLine(customer_query.Where(expression).Count());//=> 14、SQL條件語句的正則表達(dá)式和字符串求值處理
前面介紹了幾個(gè)表達(dá)式求值處理的組件,他們基本上都能夠滿足實(shí)際的求值處理,只是提供的功能有所側(cè)重。
我主要希望用它來對(duì)特定的表達(dá)式進(jìn)行求布爾值,判斷表達(dá)式是否滿足條件的。
例如對(duì)于sql條件語句:(Amount> 500 and Title ='Leader') or Age> 32, 以及一個(gè)字典對(duì)象的參數(shù)集合,我希望能夠提取里面的Amount、Title、Leader、Age這樣的鍵,然后給字典賦值,從而判斷表達(dá)式的值。
由于sql表達(dá)式和C#代碼的表達(dá)式邏輯語法有所差異,我們需要替換and Or 為實(shí)際的&& || 字符,因此給定替換的正則表達(dá)式:\sand|\sor
而我需要先提取條件語句的鍵值內(nèi)容,然后獲得指定的鍵參數(shù),那么也要提供一個(gè)正則表達(dá)式:\w*[^>=<!'()\s] ,這個(gè)正則表達(dá)式主要就是提取特定的字符匹配。

提取內(nèi)容的C#代碼邏輯如下所示。
private void btnRegexExtract_Click(object sender, EventArgs e)
{
var source = this.txtSource.Text;
//先替換部分內(nèi)容 \sand|\sor
source = Regex.Replace(source, this.txtReplaceRegex.Text, "");//替換表達(dá)式
//增加一行記錄主內(nèi)容
this.txtContent.Text += "替換正則表達(dá)式后內(nèi)容:";
this.txtContent.AppendText(Environment.NewLine);
this.txtContent.Text += source;
this.txtContent.AppendText(Environment.NewLine);
//在匹配內(nèi)容處理
var regex = new Regex(this.txtRegex.Text);
var matches = regex.Matches(source);
//遍歷獲得每個(gè)匹配的內(nèi)容
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("獲得表達(dá)式鍵:");
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=>|| '=> \" 操作符后內(nèi)容:");
this.txtContent.AppendText(Environment.NewLine);
this.txtContent.AppendText(repalce);
}/// <summary>
/// 替換And=>&& or=>|| '=> \" 操作符后內(nèi)容
/// </summary>
/// <param name="source"></param>
/// <returns></returns>
private string ReplaceExpress(string source)
{
//操作符替換表達(dá)式
var repalce = Regex.Replace(source, @"\sand\s", " && "); //and => &&
repalce = Regex.Replace(repalce, @"\sor\s", " || "); //or => ||
repalce = Regex.Replace(repalce, @"'", "\""); //'=> \"
return repalce;
}表達(dá)式處理結(jié)果如下所示

它的邏輯代碼如下。
private void btnRunExpression_Click(object sender, EventArgs e)
{
//操作符替換表達(dá)式
var repalce = ReplaceExpress(this.txtSource.Text);
this.txtContent.Text = "替換And=>&& or=>|| '=> \" 操作符后內(nèi)容:";
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("字典內(nèi)容");
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 表達(dá)式處理:");
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 表達(dá)式解析處理
this.txtContent.AppendText(Environment.NewLine);
this.txtContent.AppendText(Environment.NewLine);
this.txtContent.AppendText("DynamicExpresso 表達(dá)式解析處理:");
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);
//使用''出錯(cuò),字符串比較需要使用""
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 + ",解析出錯(cuò) => " + 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 表達(dá)式解析:");
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);
}這樣我們就可以轉(zhuǎn)換SQL條件表達(dá)式為實(shí)際的C#表達(dá)式,并通過賦值參數(shù),實(shí)現(xiàn)動(dòng)態(tài)表達(dá)式的求值處理。
到此這篇關(guān)于C#使用第三方組件實(shí)現(xiàn)動(dòng)態(tài)解析和求值字符串表達(dá)式的文章就介紹到這了,更多相關(guān)C#解析 求值字符串表達(dá)式內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
關(guān)于C#泛型列表List<T>的基本用法總結(jié)
本篇文章主要是對(duì)C#中泛型列表List<T>的基本用法進(jìn)行了詳細(xì)的總結(jié)介紹,需要的朋友可以過來參考下,希望對(duì)大家有所幫助2014-01-01
C#實(shí)現(xiàn)無限級(jí)聯(lián)下拉列表框
這篇文章主要為大家詳細(xì)介紹了C#實(shí)現(xiàn)無限級(jí)聯(lián)下拉列表框的相關(guān)資料,感興趣的小伙伴們可以參考一下2016-03-03
C#中Timer實(shí)現(xiàn)Tick使用精度的問題
這篇文章主要介紹了C#中Timer實(shí)現(xiàn)Tick使用精度的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-08-08

