表達式樹是定義代碼的數據結構。 它們基於編譯器用於分析代碼和生成已編譯輸出的相同結構。表達式樹和 Roslyn API 中用於生成分析器和 CodeFixes 的類型之間存在不少類似之處。 (分析器和 CodeFixes 是 NuGet 包,用於對代碼執行靜態分析,並可爲開發人員建議可能的修補程序。)二者概念類似,且最終結果是一種數據結構,該結構容許以有意義的方式對源代碼進行檢查。 可是,表達式樹基於一組與 Roslyn API 徹底不一樣的類和 API。html
讓咱們來舉一個簡單的示例。 如下是一個代碼行:git
var sum = 1 + 2;
若是要將其做爲一個表達式樹進行分析,則該樹包含多個節點。 最外面的節點是具備賦值 (var sum = 1 + 2;
) 的變量聲明語句,該節點包含若干子節點:變量聲明、賦值運算符和一個表示等於號右側的表達式。 該表達式被進一步細分爲表示加法運算、該加法左操做數和右操做數的表達式。github
讓咱們稍微深刻了解一下構成等於號右側的表達式。 該表達式是 1 + 2
。 這是一個二進制表達式。 更具體地說,它是一個二進制加法表達式。 二進制加法表達式有兩個子表達式,表示加法表達式的左側和右側節點。 此處的兩個節點都是常量表達式:左操做數是值 1
,右操做數是值 2
。算法
直觀地看,整個語句是一個樹:應從根節點開始,遍歷到樹中的每一個節點,以查看構成語句的代碼:express
var sum = 1 + 2;
) 的變量聲明語句
var sum
)賦值運算符 (=
)
var
)sum
)1 + 2
)
1
)+
)2
)這可能看起來很複雜,但它功能強大。 按照相同的過程,能夠分解更加複雜的表達式。 請思考此表達式:數據結構
var finalAnswer = this.SecretSauceFunction(currentState.createInterimResult(),
currentState.createSecondValue(1, 2), decisionServer.considerFinalOptions("hello")
)
+ MoreSecretSauce('A', DateTime.Now, true);
上述表達式也是具備賦值的變量聲明。 在此狀況下,賦值的右側是一棵更加複雜的樹。 我不打算分解此表達式,但請思考一下不一樣的節點多是什麼。 存在使用當前對象做爲接收方的方法調用,其中一個調用具備顯式 this
接收方,一個調用不具備此接收方。 存在使用其餘接收方對象的方法調用,存在不一樣類型的常量參數。 最後,存在二進制加法運算符。 該二進制加法運算符多是對重寫的加法運算符的方法調用(具體取決於 SecretSauceFunction()
或 MoreSecretSauce()
的返回類型),解析爲對爲類定義的二進制加法運算符的靜態方法調用。異步
儘管具備這種感知上的複雜性,但上面的表達式建立了一種樹形結構,能夠像第一個示例那樣輕鬆地導航此結構。 能夠保持遍歷子節點,以查找表達式中的葉節點。 父節點將具備對其子節點的引用,且每一個節點均具備一個用於介紹節點類型的屬性。async
表達式樹的結構很是一致。 瞭解基礎知識後,你甚至能夠理解以表達式樹形式表示的最複雜的代碼。 優美的數據結構說明了 C# 編譯器如何分析最複雜的 C# 程序並從該複雜的源代碼建立正確的輸出。ide
熟悉表達式樹的結構後,你會發現經過快速得到的知識,你可處理許多愈來愈高級的方案。 表達式樹的功能很是強大。ui
除了轉換算法以在其餘環境中執行以外,表達式樹還可用於在執行代碼前輕鬆編寫檢查代碼的算法。 能夠編寫參數爲表達式的方法,而後在執行代碼以前檢查這些表達式。 表達式樹是代碼的完整表示形式:能夠看到任何子表達式的值。 能夠看到方法和屬性名稱。 能夠看到任何常數表達式的值。 還能夠將表達式樹轉換爲可執行的委託,並執行代碼。
經過表達式樹的 API,可建立表示幾乎任何有效代碼構造的樹。 可是,出於儘量簡化的考慮,不能在表達式樹中建立某些 C# 習慣用語。 其中一個示例就是異步表達式(使用 async
和 await
關鍵字)。 若是須要異步算法,則須要直接操做 Task
對象,而不是依賴於編譯器支持。 另外一個示例是建立循環。 一般,經過使用 for
、foreach
、while
或 do
循環對其進行建立。 正如稍後能夠在本系列中看到的那樣,表達式樹的 API 支持單個循環表達式,該表達式包含控制重複循環的 break
和 continue
表達式。
不能執行的操做是修改表達式樹。 表達式樹是不可變的數據結構。 若是想要改變(更改)表達式樹,則必須建立基於原始樹副本但包含所需更改的新樹。