C# 表達式樹講解(一)

  • 2019 年 10 月 3 日
  • 筆記

一、前言

一直想寫一篇Dpper的訂製化擴展的文章,但是裡面會設計到對Lambda表達式的解析,而解析Lambda表達式,就必須要知道表達式樹的相關知識點。我希望能通過對各個模組的知識點或者運用能夠多一點的講解,能夠幫助到園友了解得更多。雖然講解得不全面,如果能成為打開這塊的一把鑰匙,也是蝸牛比較欣慰的。

表達式系列目錄

C# 表達式樹講解(一)

C# 表達式樹遍歷(二)

C# 表達式樹分頁擴展(三)

C# 表達式樹Lambda擴展(四)

二、表達樹理解

表達式樹以樹形數據結構表示程式碼,其中每一個節點都是一種表達式,它將我們原來可以直接由程式碼編寫的邏輯以表達式的方式存儲在樹狀的結構里,從而可以在運行時去解析這個樹,然後執行,實現動態的編輯和執行程式碼。在.Net 裡面的Linq to SQL就是對表達式樹的解析。

這裡先講解下表達式和表達式樹,表達式相信大家都知道,比如x+5或者5,都可以算是表達式,而表達式樹裡面的樹指的二叉樹,也就是表達式的集合,C#中的Expression類就是表達式類。對於一棵表達式樹,其葉子節點都是參數或者常數,非葉子節點都是運算符或者控制符。

2.1、表達式的創建

Lambda表達式方法:

Expression<Func<int, int,bool>> fun = (x, y) => x < y

這種方法創建出的表達式根節點類型為ExpressionType.Lambda,Type類型為返回值類型typeof(bool)

組裝法(通過 API 創建表達式樹):

ParameterExpression numParam = Expression.Parameter(typeof(int), "num");  ConstantExpression five = Expression.Constant(5, typeof(int));  BinaryExpression numLessThanFive = Expression.LessThan(numParam, five);  Expression<Func<int, bool>> lambda1 =      Expression.Lambda<Func<int, bool>>(          numLessThanFive,          new ParameterExpression[] { numParam });

我們先創建了兩個參數表達式num和5,然後用LessThan組裝在一起,最終的表達式為“num<5”,expr的節點類型為LessThan,Type類型為typeof(bool)

我們先看看錶達式樹裡面的構造

首先Expression<TDelegate>的功能是將強類型Lambda表達式表示為表達式樹形式的數據結構,他的父類是LambdaExpression,比較他們程式碼可知,Lambda表達式的主體,名稱和參數全部保存在LambdaExpression裡面。

Expression<TDelegate>與LambdaExpression程式碼截圖:

image

image

LambdaExpression裡面的Body就是我們的表達式。

C#表達式給我們提供了豐富的表達式類,進入到LambdaExpression類裡面

image

方法返回類型以“Expression”結尾的,基本上都是一個表達式類。

每個表達式代表的定義和創建方法,可以參照微軟官方文檔https://docs.microsoft.com/zh-cn/dotnet/api/system.linq.expressions.binaryexpression?view=netframework-4.8

下面是平常使用最多的表達式

ConstantExpression:常量表達式

ParameterExpression:參數表達式

UnaryExpression:一元運算符表達式

BinaryExpression:二元運算符表達式

TypeBinaryExpression:is運算符表達式

ConditionalExpression:條件表達式

MemberExpression:訪問欄位或屬性表達式

MethodCallExpression:調用成員函數表達式

Expression<TDelegate>:委託表達式

2.2、表達式的解析

表達式樹解析

通過LambdaExpression類我們可以知道,表達式樹包含:參數[Parameters],表達式樹類型[NodeType],表達式[Body],返回類型[ReturnType],Lambda表達式的委託[Compile]以及Lambda表達式名稱[name],如圖所示:

image

表達式解析:

所有的表達式都包含:左節點【Left】,右節點【Right】,類型【NodeType】,不同的表達式還會有其他屬性,這裡的左右節點依舊是表達式。

下圖是BinaryExpression表達式截圖

image

表達式樹和表達式裡面的類型NodeType是一個枚舉,一共有85個類型,有興趣的朋友可以去了解下。

常用的類型如下:

ExpressionType.And:C#中類似於&

ExpressionType.AndAlso:C#中類似於&&

ExpressionType.Or:C#中類似於|

ExpressionType.OrElse:C#中類似於||

ExpressionType.Equal:C#中類似於==

ExpressionType.NotEqual:C#中類似於!=

ExpressionType.GreaterThan:C#中類似於>

ExpressionType.GreaterThanOrEqual:C#中類似於>=

ExpressionType.LessThan:C#中類似於<

ExpressionType.LessThanOrEqual:C#中類似於<=

ExpressionType.Add:C#中類似於+

ExpressionType.AddChecked:C#中類似於+

ExpressionType.Subtract:C#中類似於-

ExpressionType.SubtractChecked:C#中類似於-

ExpressionType.Divide:C#中類似於/

ExpressionType.Multiply:C#中類似於*

ExpressionType.MultiplyChecked:C#中類似於*

2.3、編譯表達式樹

在表達式創建那,我們組合創建了一個Lambda表達式,那麼應該怎麼使用它呢?在“表達式的解析”裡面,LambdaExpression類和Expression<TDelegate>類都有一個Compile的方法,學名是Lambda表達式的委託,其實就是Lambda表達式編譯函數的委託,所以我們只需要調用他,得到的結果就是一個函數方法。

程式碼修改如下:

ParameterExpression numParam = Expression.Parameter(typeof(int), "num");  ConstantExpression five = Expression.Constant(5, typeof(int));  BinaryExpression numLessThanFive = Expression.LessThan(numParam, five);  Expression<Func<int, bool>> lambda1 =      Expression.Lambda<Func<int, bool>>(          numLessThanFive,          new ParameterExpression[] { numParam });    Console.WriteLine($"Lambda的內容:{lambda1.ToString()}");    //表達式的編譯  var func = lambda1.Compile();  Console.WriteLine($"Lambda的運行結果:{func(6)}");  

運行結果

image

三、總結

這裡我們對表達式做了基本的講解,相信大家對Lambda表達式有了初步的了解,下面我們將繼續講解對一個表達式樹的遍歷。