說明運算式樹狀架構Expression Trees Explained

上一個主題 -- 概觀Previous -- Overview

運算式樹狀架構是一種定義程式碼的資料結構。An Expression Tree is a data structure that defines code. 其所依據的結構,與編譯器分析程式碼和產生編譯輸出所使用的結構相同。They are based on the same structures that a compiler uses to analyze code and generate the compiled output. 當您閱讀完本教學課程之後,您將會發現運算式樹狀架構與 Roslyn API 中用來建立分析器和程式碼修正的類型相當類似As you read through this tutorial, you will notice quite a bit of similarity between Expression Trees and the types used in the Roslyn APIs to build Analyzers and CodeFixes. (分析器和代碼修復程式是 NuGet 包,用於對代碼執行靜態分析,並可為開發人員建議潛在的修補程式。概念相似,最終結果是資料結構允許以有意義的方式檢查原始程式碼。(Analyzers and CodeFixes are NuGet packages that perform static analysis on code and can suggest potential fixes for a developer.) The concepts are similar, and the end result is a data structure that allows examination of the source code in a meaningful way. 不過,運算式樹狀架構是根據一組與 Roslyn API 完全不同的類別和 API。However, Expression Trees are based on a totally different set of classes and APIs than the Roslyn APIs.

讓我們來看一個簡單的範例。Let's look at a simple example. 程式碼行如下:Here's a line of code:

var sum = 1 + 2;

如果您想要以運算式樹狀架構來分析此程式碼,此樹狀會包含數個節點。If you were to analyze this as an expression tree, the tree contains several nodes. 最外側節點是具有指派的變數宣告陳述式 (var sum = 1 + 2;)。該最外側節點包含數個子節點︰變數宣告、指派運算子,以及代表等號右邊的運算式。The outermost node is a variable declaration statement with assignment (var sum = 1 + 2;) That outermost node contains several child nodes: a variable declaration, an assignment operator, and an expression representing the right hand side of the equals sign. 此運算式會進一步細分為代表加法運算,以及加法左右運算元的運算式。That expression is further subdivided into expressions that represent the addition operation, and left and right operands of the addition.

讓我們向下切入以深入了解構成等號右邊的運算式。Let's drill down a bit more into the expressions that make up the right side of the equals sign. 此運算式為 1 + 2The expression is 1 + 2. 這是二元運算式。That's a binary expression. 更具體來說,這是二元加法運算式。More specifically, it's a binary addition expression. 二元加法運算式具有兩個子系,分別代表加法運算式的左右節點。A binary addition expression has two children, representing the left and right nodes of the addition expression. 在這裡,這兩個節點都是常數運算式︰左運算元是值 1,右運算元是值 2Here, both nodes are constant expressions: The left operand is the value 1, and the right operand is the value 2.

從外表來看,整個陳述式就是一個樹狀︰您可以從根節點開始,然後周遊樹狀中的每個節點,以查看構成陳述式的程式碼:Visually, the entire statement is a tree: You could start at the root node, and travel to each node in the tree to see the code that makes up the statement:

  • 具有指派的變數宣告陳述式 (var sum = 1 + 2;)Variable declaration statement with assignment (var sum = 1 + 2;)
    • 隱含變數類型宣告 (var sum)Implicit variable type declaration (var sum)
      • 隱含 var 關鍵字 (var)Implicit var keyword (var)
      • 變數名稱宣告 (sum)Variable name declaration (sum)
    • 指派運算子 (=)Assignment operator (=)
    • 二元加法運算式 (1 + 2)Binary addition expression (1 + 2)
      • 左運算元 (1)Left operand (1)
      • 加法運算子 (+)Addition operator (+)
      • 右運算元 (2)Right operand (2)

雖然這看起來可能很複雜,其功能卻很強大。This may look complicated, but it is very powerful. 您可以遵循相同的程序來分解較為複雜的運算式。Following the same process, you can decompose much more complicated expressions. 以下列運算式為例:Consider this expression:

var finalAnswer = this.SecretSauceFunction(
    currentState.createInterimResult(), currentState.createSecondValue(1, 2),
    decisionServer.considerFinalOptions("hello")) +
    MoreSecretSauce('A', DateTime.Now, true);

上述運算式也是具有指派的變數宣告。The expression above is also a variable declaration with an assignment. 在此例中,指派的右邊是較為複雜的樹狀。In this instance, the right hand side of the assignment is a much more complicated tree. 我不要分解此運算式,而是考慮可能會有哪些不同的節點。I'm not going to decompose this expression, but consider what the different nodes might be. 其中包含使用目前物件作為接收器的方法呼叫,其中一個具有明確的 this 接收器,另一個則沒有。There are method calls using the current object as a receiver, one that has an explicit this receiver, one that does not. 同時包含使用其他接收器物件的方法呼叫,以及不同類型的常數引數。There are method calls using other receiver objects, there are constant arguments of different types. 最後還有一個二元加法運算子。And finally, there is a binary addition operator. 根據 SecretSauceFunction()MoreSecretSauce() 的傳回型別,該二元加法運算子可能會是已覆寫加法運算子的方法呼叫,或解析為針對某個類別定義之二元加法運算子的靜態呼叫。Depending on the return type of SecretSauceFunction() or MoreSecretSauce(), that binary addition operator may be a method call to an overridden addition operator, resolving to a static method call to the binary addition operator defined for a class.

儘管這很複雜,但上述運算式所建立的樹狀就和第一個範例一樣可以輕鬆地巡覽。Despite this perceived complexity, the expression above creates a tree structure that can be navigated as easily as the first sample. 您可以繼續周遊子節點,以尋找運算式中的分葉節點。You can keep traversing child nodes to find leaf nodes in the expression. 父節點都會有其子系的參考,而且每個節點都會有描述節點所屬類型的屬性。Parent nodes will have references to their children, and each node has a property that describes what kind of node it is.

運算式樹狀架構的結構非常一致。The structure of an expression tree is very consistent. 一旦您了解基本概念,即使是以運算式樹狀架構表示的最複雜程式碼,您也能瞭如指掌。Once you've learned the basics, you can understand even the most complex code when it is represented as an expression tree. 資料結構的簡潔說明 C# 編譯器可如何分析最複雜的 C# 程式,並從該複雜的原始程式碼建立適當的輸出。The elegance in the data structure explains how the C# compiler can analyze the most complex C# programs and create proper output from that complicated source code.

一旦您熟悉運算式樹狀架構的結構,您將會發現所得到的知識可讓您快速使用許多更進階的案例。Once you become familiar with the structure of expression trees, you will find that knowledge you've gained quickly enables you to work with many more and more advanced scenarios. 運算式樹狀架構的功能很強大。There is incredible power to expression trees.

除了轉譯要在其他環境中執行的演算法,運算式樹狀架構還可用來更輕鬆地撰寫在執行前檢查程式碼的演算法。In addition to translating algorithms to execute in other environments, expression trees can be used to make it easier to write algorithms that inspect code before executing it. 您可以撰寫其引數為運算式的方法,然後查看這些運算式,再執行程式碼。You can write a method whose arguments are expressions and then examine those expressions before executing the code. 運算式樹狀架構是程式碼的完整表示︰您可以查看任何子運算式的值。The Expression Tree is a full representation of the code: you can see values of any sub-expression. 您可以查看方法和屬性名稱。You can see method and property names. 您可以查看任何常數運算式的值。You can see the value of any constant expressions. 您也可以將運算式樹狀架構轉換成可執行委派,再執行程式碼。You can also convert an expression tree into an executable delegate, and execute the code.

運算式樹狀架構的 API 可讓您建立表示幾乎所有有效程式碼建構的樹狀架構。The APIs for Expression Trees enable you to create trees that represent almost any valid code construct. 不過,為了盡可能保持簡單,您將無法在運算式樹狀架構中建立某些 C# 慣用語。However, to keep things as simple as possible, some C# idioms cannot be created in an expression tree. 其中一個範例是非同步運算式 (使用 asyncawait 關鍵字)。One example is asynchronous expressions (using the async and await keywords). 如果您的需求需要非同步演算法,您必須直接操作 Task 物件,而不是依賴編譯器支援。If your needs require asynchronous algorithms, you would need to manipulate the Task objects directly, rather than rely on the compiler support. 另一個範例是建立迴圈。Another is in creating loops. 通常,您會使用 forforeachwhiledo 迴圈來建立這些迴圈。Typically, you create these by using for, foreach, while or do loops. 如您在本系列稍後所見,運算式樹狀架構的 API 支援單一迴圈運算式,並使用 breakcontinue 來控制重複迴圈。As you'll see later in this series, the APIs for expression trees support a single loop expression, with break and continue expressions that control repeating the loop.

您無法執行的作業是修改運算式樹狀架構。The one thing you can't do is modify an expression tree. 運算式樹狀架構是不可變的資料結構。Expression Trees are immutable data structures. 如果您想要變動 (變更) 運算式樹狀架構,您必須複製原始樹狀並進行所需的變更,以建立新的樹狀。If you want to mutate (change) an expression tree, you must create a new tree that is a copy of the original, but with your desired changes.

下一個主題 -- 支援運算式樹狀架構的架構類型Next -- Framework Types Supporting Expression Trees