C#表达式树浅析
一、前言
? ?? 在我們?nèi)粘i_發(fā)中Lamba 表達(dá)式經(jīng)常會(huì)使用,如List.Where(n=>Name="abc") 使用起來非常的方便,代碼也很簡(jiǎn)潔,總之一個(gè)字就是“爽”。在之前我們總是用硬編碼的方式去實(shí)現(xiàn)一些底層方法,比如我要查詢用戶“abc”是否存在,老的實(shí)現(xiàn)方式基本為:GetUser(string name) ,可能下次我需要按用戶登錄賬號(hào)來查詢,于是又得寫一個(gè)方法:GetUserByLogin(string loginCode),我們認(rèn)真想一下,如果能實(shí)現(xiàn)類似于集合查詢那樣只要寫lambda 就能搞定,List.Where(n=>Name="abc"),List.Where(n=>LoginCode=="小A"),有了這樣的想法,那我們也去了解一下lambda 表達(dá)式樹的背后吧。
二、初識(shí)
? ? ? 表達(dá)式樹關(guān)鍵字“Expressions”,我們?cè)诠俜轿臋n里面可以看到很多介紹,具體信息請(qǐng)查看微軟官方文檔庫(kù);官方文檔里面的信息量比較大,有幾十個(gè)對(duì)象的介紹:
這里我不建議大家從頭到尾的看一遍,大致瀏覽就好了,因?yàn)樾畔⒘刻嗔恕J紫任覀冃陆ㄒ粋€(gè)控制臺(tái)程序,框架版本選FX4.0以上或者Core 都行,引入命名空間:
using System.Linq.Expressions; 接下來實(shí)現(xiàn)一個(gè)簡(jiǎn)單的功能,解析表達(dá)式: n=>n.Name="abc" 我們想要的結(jié)果是 Name="abc",有了這個(gè)目標(biāo)我們就知道該干嘛了。
定義函數(shù):private static string GetLambdaStr<T>(Expression<Func<T, bool>> expression),該函數(shù)定義了一個(gè)表達(dá)式樹參數(shù),Func<in T,out bool>是范型委托,該委托表示接收一個(gè)T類型參數(shù),返回一個(gè)bool值。具體代碼:
private static string GetLambdaStr<T>(Expression<Func<T, bool>> expression){//解析表達(dá)式//return new Analysis().AnalysisExpression(expression);return null;}
?接下來建立一個(gè)用戶對(duì)象:
public class User{public int ID { get; set; }public string Name { get; set; }public int Age { get; set; }public bool States { get; set; }}再建立好測(cè)試代碼:
//Expression<Func<T, bool>> lambda = n => n.Name == "abc";Console.WriteLine("lambda : n => n.Name == \"abc\" ");var a = GetLambdaStr<User>(n => n.Name == "abc");Console.WriteLine("result:" + a);Console.Write(Environment.NewLine);Console.ReadKey();先不管那么多,我們調(diào)試進(jìn)去看看表達(dá)式長(zhǎng)啥樣:
?
這樣看比較清真,就是一個(gè)lambda表達(dá)式,我們展開看看對(duì)象明細(xì):
?
看到這里我們是不是能想起點(diǎn)什么了,這其實(shí)就是一顆二叉樹,顯示的層次為根節(jié)點(diǎn),左子節(jié)點(diǎn),右子節(jié)點(diǎn),依次循環(huán)。有了這個(gè)認(rèn)知,我們立馬能想到可以使用遞歸來遍歷節(jié)點(diǎn)了。
? ?? 于是我來了解表達(dá)式對(duì)象“Expression”有哪些屬性和方法:
看到這里有點(diǎn)困惑了,剛剛我們明明看到有Left、Right 屬性,但這里卻沒有,感覺好坑呀。沒有左右節(jié)點(diǎn)我們根本不知道怎么去遞歸查找子節(jié)點(diǎn)呢。于是又去看官方介紹文檔了,然后再仔細(xì)看了LambdaExpression 對(duì)象,這個(gè)是抽象類,有抽象必定有相關(guān)的實(shí)現(xiàn)或者提供對(duì)外屬性,仔細(xì)一找,剛好找到BinaryExpression對(duì)象,有Left、Right屬性同時(shí)繼承了Expression對(duì)象,也提供了LambdaExpression 屬性,這個(gè)就是我們要找的對(duì)象了,可以說是峰回路轉(zhuǎn)了:
?順著這個(gè)思路,我又找到了屬性成員和屬性值對(duì)象MemberExpression、ConstantExpression,我們來實(shí)現(xiàn)關(guān)鍵代碼
??????? private static string GetLambdaStr<T>(Expression<Func<T, bool>> expression)??????? {
??????????? //解析表達(dá)式
??????????? var body = (BinaryExpression)expression.Body;
??????????? var r = (ConstantExpression)body.Right;
??????????? var l = (MemberExpression)body.Left;
??????????? var value = r.Type.IsValueType ? r.Value : $"'{r.Value}'";
??????????? return $"{ l.Member.Name} {Operand(body.NodeType)} {value} ";
??????????? // return new Analysis().AnalysisExpression(expression);
??????? }
Operand 是操作類型轉(zhuǎn)換,代碼如下:
//操作符轉(zhuǎn)換private string Operand(ExpressionType type){string operand = string.Empty;switch (type){case ExpressionType.AndAlso:operand = "AND";break;case ExpressionType.OrElse:operand = "OR";break;case ExpressionType.Equal:operand = "=";break;case ExpressionType.LessThan:operand = "<";break;case ExpressionType.LessThanOrEqual:operand = "<=";break;case ExpressionType.GreaterThan:operand = ">";break;case ExpressionType.GreaterThanOrEqual:operand = ">";break;}return operand;} View Code有了上面的代碼我們已經(jīng)完成功能了,運(yùn)行結(jié)果如下:
三、進(jìn)階
? ?? 日常開發(fā)中我們遇到的查詢條件可能會(huì)更加復(fù)雜,于是我又寫了幾個(gè)復(fù)雜得表達(dá)式:
//Expression<Func<T, bool>> lambda = n => n.states;Console.WriteLine("analysis: n => n.states ");var b = GetLambdaStr<User>(n => n.States);Console.WriteLine("result:" + b);Console.ReadKey();//Expression<Func<T, bool>> lambda = n => n.Name == "abc" && n.Age > 30 || n.ID == 4;Console.WriteLine("lambda: n => n.Name == \"abc\" && n.Age > 30 || n.ID == 4");var c = GetLambdaStr<User>(n => n.Name == "abc" && (n.Age > 30 || n.ID == 4) && n.ID > 1&& (n.ID > 19 || n.Name == "33"));Console.WriteLine("result:" + c);Console.Write(Environment.NewLine);Console.ReadKey();
?經(jīng)過我的一番探索和調(diào)試,終于完成了解析:
建議手動(dòng)去敲一遍代碼,并調(diào)試,這中間我遇到了一些坑,比如使用了OR條件時(shí)需要增加括號(hào),這個(gè)括號(hào)老是沒放對(duì)位置。
最后貼出全部代碼:
1、控制臺(tái)代碼
//Expression<Func<T, bool>> lambda = n => n.Name == "abc";Console.WriteLine("lambda : n => n.Name == \"abc\" ");var a = GetLambdaStr<User>(n => n.Name == "abc");Console.WriteLine("result:" + a);Console.Write(Environment.NewLine);Console.ReadKey();//Expression<Func<T, bool>> lambda = n => n.states;Console.WriteLine("analysis: n => n.states ");var b = GetLambdaStr<User>(n => n.States);Console.WriteLine("result:" + b);Console.ReadKey();//Expression<Func<T, bool>> lambda = n => n.Name == "abc" && n.Age > 30 || n.ID == 4;Console.WriteLine("lambda: n => n.Name == \"abc\" && n.Age > 30 || n.ID == 4");var c = GetLambdaStr<User>(n => n.Name == "abc" && (n.Age > 30 || n.ID == 4) && n.ID > 1 && (n.ID > 19 || n.Name == "33"));Console.WriteLine("result:" + c);Console.Write(Environment.NewLine);Console.ReadKey(); View Code2、解析函數(shù)
private static string GetLambdaStr<T>(Expression<Func<T, bool>> expression){//解析表達(dá)式return new Analysis().AnalysisExpression(expression);} View Code3、用戶對(duì)象代碼上面已經(jīng)有了就不重復(fù)發(fā)了
4、解析對(duì)象代碼
public class Analysis{private StringBuilder builder = new StringBuilder();public string AnalysisExpression<TDelegate>(Expression<TDelegate> expression){if (expression.Body is MemberExpression){var m = (MemberExpression)expression.Body;var value = Convert.ToInt32(!expression.Body.ToString().Contains("!"));builder.Append($" ({m.Member.Name}={value}) ");return builder.ToString();}var body = (BinaryExpression)expression.Body;if (body.NodeType == ExpressionType.AndAlso || body.NodeType == ExpressionType.OrElse){AnalysisExpressionChild((BinaryExpression)body.Left, body.NodeType);AnalysisExpressionChild((BinaryExpression)body.Right, body.NodeType);}else{var r = (ConstantExpression)body.Right;var l = (MemberExpression)body.Left;var value = r.Type.IsValueType ? r.Value : $"'{r.Value}'";builder.Append($" { l.Member.Name} {Operand(body.NodeType)} {value} ");}return builder.ToString();}//解析表達(dá)式樹private void AnalysisExpressionChild(BinaryExpression expression, ExpressionType pType, string brackets = ""){if (expression.NodeType != ExpressionType.AndAlso && expression.NodeType != ExpressionType.OrElse){var r = (ConstantExpression)expression.Right;var l = (MemberExpression)expression.Left;var value = r.Type.IsValueType ? r.Value : $"'{r.Value}'";builder.Append($" {Operand(pType)} {brackets} { l.Member.Name} {Operand(expression.NodeType)} {value} ");}else{if (expression.NodeType == ExpressionType.OrElse){brackets = "( ";}AnalysisExpressionChild((BinaryExpression)expression.Left, pType, brackets);AnalysisExpressionChild((BinaryExpression)expression.Right, expression.NodeType);if (expression.NodeType == ExpressionType.OrElse){builder.Append(" )");}}}//操作符轉(zhuǎn)換private string Operand(ExpressionType type){string operand = string.Empty;switch (type){case ExpressionType.AndAlso:operand = "AND";break;case ExpressionType.OrElse:operand = "OR";break;case ExpressionType.Equal:operand = "=";break;case ExpressionType.LessThan:operand = "<";break;case ExpressionType.LessThanOrEqual:operand = "<=";break;case ExpressionType.GreaterThan:operand = ">";break;case ExpressionType.GreaterThanOrEqual:operand = ">";break;}return operand;}} View Code?至此,表達(dá)式樹已經(jīng)完成了解析,上面的案例已經(jīng)能滿足常用的需求了,若有其他要求我們可以繼續(xù)改造拓展解析方法。
四、總結(jié)
? ? ? 我們?cè)趯W(xué)習(xí)技術(shù)的時(shí)候帶著一定的目的去學(xué)習(xí)往往效率更高,又不容易忘記,同時(shí)要善于思考,聯(lián)系上下文情景。如果你覺得看完后對(duì)你有幫助可以給我點(diǎn)贊。
?
相關(guān)代碼已經(jīng)放到GitHub?
參考文檔:
1、微軟官方文檔庫(kù):https://docs.microsoft.com/zh-cn/dotnet/api/system.linq.expressions?view=netcore-2.2
2、騰訊云:https://cloud.tencent.com/developer/article/1334993
五、補(bǔ)充
? ?? 經(jīng)過繼續(xù)研究和分析網(wǎng)友的解析方法,發(fā)現(xiàn)其實(shí)微軟對(duì)表達(dá)式解析已經(jīng)提供的了一個(gè)專門類:ExpressionVisitor,該類實(shí)現(xiàn)了對(duì)各種表達(dá)式操作的解析,我們直接繼承它, 所以我又重寫了一個(gè)解析類:AnalyseExpressionHelper,需要修改案例中調(diào)用解析方法的代碼:
?
private static string GetLambdaStr<T>(Expression<Func<T, bool>> expression){//解析表達(dá)式三var heler = new AnalyseExpressionHelper();heler.AnalyseExpression(expression);return heler.Result;}?
操作符轉(zhuǎn)換函數(shù)已經(jīng)修改為拓展方法:
using System.Linq.Expressions;namespace ExpressionTreeDemo {public static class ExpressionExtend{//操作符轉(zhuǎn)換public static string TransferOperand(this ExpressionType type){string operand = string.Empty;switch (type){case ExpressionType.AndAlso:operand = "AND";break;case ExpressionType.OrElse:operand = "OR";break;case ExpressionType.Equal:operand = "=";break;case ExpressionType.NotEqual:operand = "<>";break;case ExpressionType.LessThan:operand = "<";break;case ExpressionType.LessThanOrEqual:operand = "<=";break;case ExpressionType.GreaterThan:operand = ">";break;case ExpressionType.GreaterThanOrEqual:operand = ">=";break;}return operand;}} } ExpressionExtend?
新的表達(dá)式解析方法:
using System; using System.Text; using System.Linq.Expressions;namespace ExpressionTreeDemo {/// <summary>/// 表達(dá)式解析輔助類/// </summary>public class AnalyseExpressionHelper : ExpressionVisitor{private StringBuilder express = new StringBuilder();public string Result { get { return express.ToString(); } }public void AnalyseExpression<T>(Expression<Func<T, bool>> expression){Visit(expression.Body);}protected override Expression VisitBinary(BinaryExpression node){if (node.NodeType == ExpressionType.OrElse)express.Append("(");Visit(node.Left);express.Append($" {node.NodeType.TransferOperand()} ");Visit(node.Right);if (node.NodeType == ExpressionType.OrElse)express.Append(")");return node;}protected override Expression VisitConstant(ConstantExpression node){if (node.Type.IsValueType && node.Type != typeof(DateTime)){express.Append(node.Value);}else{express.Append($"'{node.Value}'");}return node;}protected override Expression VisitMember(MemberExpression node){express.Append(node.Member.Name);return node;}} } AnalyseExpressionHelper?
轉(zhuǎn)載于:https://www.cnblogs.com/heweijian/p/11406715.html
總結(jié)
- 上一篇: axios拦截器的实现
- 下一篇: C#与Unity 数据存储