C#表達式樹淺析
- 2019 年 10 月 3 日
- 筆記
一、前言
在我們日常開發中Lamba 表達式經常會使用,如List.Where(n=>Name=”abc”) 使用起來非常的方便,程式碼也很簡潔,總之一個字就是“爽”。在之前我們總是用硬編碼的方式去實現一些底層方法,比如我要查詢用戶“abc”是否存在,老的實現方式基本為:GetUser(string name) ,可能下次我需要按用戶登錄帳號來查詢,於是又得寫一個方法:GetUserByLogin(string loginCode),我們認真想一下,如果能實現類似於集合查詢那樣只要寫lambda 就能搞定,List.Where(n=>Name=”abc”),List.Where(n=>LoginCode==”小A”),有了這樣的想法,那我們也去了解一下lambda 表達式樹的背後吧。
二、初識
表達式樹關鍵字“Expressions”,我們在官方文檔裡面可以看到很多介紹,具體資訊請查看微軟官方文檔庫;官方文檔裡面的資訊量比較大,有幾十個對象的介紹:
這裡我不建議大家從頭到尾的看一遍,大致瀏覽就好了,因為資訊量太多了。首先我們新建一個控制台程式,框架版本選FX4.0以上或者Core 都行,引入命名空間:
using System.Linq.Expressions; 接下來實現一個簡單的功能,解析表達式: n=>n.Name=”abc” 我們想要的結果是 Name=”abc”,有了這個目標我們就知道該幹嘛了。
定義函數:private static string GetLambdaStr<T>(Expression<Func<T, bool>> expression),該函數定義了一個表達式樹參數,Func<in T,out bool>是范型委託,該委託表示接收一個T類型參數,返回一個bool值。具體程式碼:
private static string GetLambdaStr<T>(Expression<Func<T, bool>> expression) { //解析表達式 //return new Analysis().AnalysisExpression(expression);
return null; }
接下來建立一個用戶對象:
public class User { public int ID { get; set; } public string Name { get; set; } public int Age { get; set; } public bool States { get; set; } }
再建立好測試程式碼:
//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();
先不管那麼多,我們調試進去看看錶達式長啥樣:
這樣看比較清真,就是一個lambda表達式,我們展開看看對象明細:
看到這裡我們是不是能想起點什麼了,這其實就是一顆二叉樹,顯示的層次為根節點,左子節點,右子節點,依次循環。有了這個認知,我們立馬能想到可以使用遞歸來遍歷節點了。
於是我來了解表達式對象“Expression”有哪些屬性和方法:
看到這裡有點困惑了,剛剛我們明明看到有Left、Right 屬性,但這裡卻沒有,感覺好坑呀。沒有左右節點我們根本不知道怎麼去遞歸查找子節點呢。於是又去看官方介紹文檔了,然後再仔細看了LambdaExpression 對象,這個是抽象類,有抽象必定有相關的實現或者提供對外屬性,仔細一找,剛好找到BinaryExpression對象,有Left、Right屬性同時繼承了Expression對象,也提供了LambdaExpression 屬性,這個就是我們要找的對象了,可以說是峰迴路轉了:
順著這個思路,我又找到了屬性成員和屬性值對象MemberExpression、ConstantExpression,我們來實現關鍵程式碼
private static string GetLambdaStr<T>(Expression<Func<T, bool>> expression)
{
//解析表達式
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 是操作類型轉換,程式碼如下:

//操作符轉換 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
有了上面的程式碼我們已經完成功能了,運行結果如下:
三、進階
日常開發中我們遇到的查詢條件可能會更加複雜,於是我又寫了幾個複雜得表達式:
//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();
經過我的一番探索和調試,終於完成了解析:
建議手動去敲一遍程式碼,並調試,這中間我遇到了一些坑,比如使用了OR條件時需要增加括弧,這個括弧老是沒放對位置。
最後貼出全部程式碼:
1、控制台程式碼

//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 Code
2、解析函數

private static string GetLambdaStr<T>(Expression<Func<T, bool>> expression) { //解析表達式 return new Analysis().AnalysisExpression(expression); }
View Code
3、用戶對象程式碼上面已經有了就不重複發了
4、解析對象程式碼

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(); } //解析表達式樹 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(" )"); } } } //操作符轉換 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
至此,表達式樹已經完成了解析,上面的案例已經能滿足常用的需求了,若有其他要求我們可以繼續改造拓展解析方法。
四、總結
我們在學習技術的時候帶著一定的目的去學習往往效率更高,又不容易忘記,同時要善於思考,聯繫上下文情景。如果你覺得看完後對你有幫助可以給我點贊。
相關程式碼已經放到GitHub
參考文檔:
1、微軟官方文檔庫:https://docs.microsoft.com/zh-cn/dotnet/api/system.linq.expressions?view=netcore-2.2
2、騰訊云:https://cloud.tencent.com/developer/article/1334993
五、補充
經過繼續研究和分析網友的解析方法,發現其實微軟對表達式解析已經提供的了一個專門類:ExpressionVisitor,該類實現了對各種表達式操作的解析,我們直接繼承它, 所以我又重寫了一個解析類:AnalyseExpressionHelper,需要修改案例中調用解析方法的程式碼:
private static string GetLambdaStr<T>(Expression<Func<T, bool>> expression) { //解析表達式三 var heler = new AnalyseExpressionHelper(); heler.AnalyseExpression(expression); return heler.Result; }
操作符轉換函數已經修改為拓展方法:

using System.Linq.Expressions; namespace ExpressionTreeDemo { public static class ExpressionExtend { //操作符轉換 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
新的表達式解析方法:

using System; using System.Text; using System.Linq.Expressions; namespace ExpressionTreeDemo { /// <summary> /// 表達式解析輔助類 /// </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