C#3.0新增功能10 表達式樹 06 生成表達式

到目前爲止,你所看到的全部表達式樹都是由 C# 編譯器建立的。 你所要作的是建立一個 lambda 表達式,將其分配給一個類型爲 Expression<Func<T>> 或某種類似類型的變量。 這不是建立表達式樹的惟一方法。 不少狀況下,可能須要在運行時在內存中生成一個表達式。html

因爲這些表達式樹是不可變的,因此生成表達式樹很複雜。 不可變意味着必須以從葉到根的方式生成表達式樹。 用於生成表達式樹的 API 體現了這一點:用於生成節點的方法將其全部子級用做參數。 讓咱們經過幾個示例來了解相關技巧。async

建立節點
從相對簡單的內容開始。 咱們將使用在這些部分中一直使用的加法表達式:
Expression<Func<int>> sum = () => 1 + 2;

若要構造該表達式樹,必須構造葉節點。 葉節點是常量,所以可使用 Expression.Constant 方法建立節點:oop

var one = Expression.Constant(1, typeof(int));
var two = Expression.Constant(2, typeof(int));

接下來,將生成加法表達式:測試

var addition = Expression.Add(one, two);

一旦得到了加法表達式,就能夠建立 lambda 表達式:spa

var lambda = Expression.Lambda(addition);

這是一個很是簡單的 Lambda 表達式,由於它不包含任何參數。 在本節的後續部分,你將瞭解如何將實參映射到形參並生成更復雜的表達式。code

對於此類簡單的表達式,能夠將全部調用合併到單個語句中:htm

var lambda = Expression.Lambda(
    Expression.Add(Expression.Constant(1, typeof(int)),
                   Expression.Constant(2, typeof(int))
                  )
);
生成樹

這是在內存中生成表達式樹的基礎知識。 更復雜的樹一般意味着更多的節點類型,而且樹中有更多的節點。 讓咱們再瀏覽一個示例,瞭解一般在建立表達式樹時建立的其餘兩個節點類型:參數節點和方法調用節點。對象

生成一個表達式樹以建立此表達式:blog

Expression<Func<double, double, double>> distanceCalc = (x, y) => Math.Sqrt(x * x + y * y);

首先,建立 x 和 y 的參數表達式:ip

var xParameter = Expression.Parameter(typeof(double), "x");
var yParameter = Expression.Parameter(typeof(double), "y");

按照你所看到的模式建立乘法和加法表達式:

var xSquared = Expression.Multiply(xParameter, xParameter);
var ySquared = Expression.Multiply(yParameter, yParameter);
var sum = Expression.Add(xSquared, ySquared);

接下來,須要爲調用 Math.Sqrt 建立方法調用表達式。

var sqrtMethod = typeof(Math).GetMethod("Sqrt", new[] { typeof(double) });
var distance = Expression.Call(sqrtMethod, sum);

最後,將方法調用放入 lambda 表達式,並確保定義 lambda 表達式的參數:

var distanceLambda = Expression.Lambda(distance,xParameter, yParameter);

在這個更復雜的示例中,你看到了建立表達式樹一般使用的其餘幾種技巧。

首先,在使用它們以前,須要建立表示參數或局部變量的對象。 建立這些對象後,能夠在表達式樹中任何須要的位置使用它們。

其次,須要使用反射 API 的一個子集來建立 MethodInfo 對象,以便建立表達式樹以訪問該方法。 必須僅限於 .NET Core 平臺上提供的反射 API 的子集。 一樣,這些技術將擴展到其餘表達式樹。

深度生成代碼

不只限於使用這些 API 能夠生成的代碼。 可是,要生成的表達式樹越複雜,代碼就越難以管理和閱讀。

讓咱們生成一個與此代碼等效的表達式樹:

Func<int, int> factorialFunc = (n) =>
{
    var res = 1;
    while (n > 1)
    {
        res = res * n;
        n--;
    }
    return res;
};

請注意上面我未生成表達式樹,只是生成了委託。 使用 Expression 類不能生成語句 lambda。 下面是生成相同的功能所需的代碼。 它很複雜,這是由於沒有用於生成 while循環的 API,而是須要生成一個包含條件測試的循環和一個用於中斷循環的標籤目標。

 1 var nArgument = Expression.Parameter(typeof(int), "n");
 2 var result = Expression.Variable(typeof(int), "result");
 3 
 4 // 建立一個表示返回值的標籤
 5 LabelTarget label = Expression.Label(typeof(int));
 6 
 7 var initializeResult = Expression.Assign(result, Expression.Constant(1));
 8 
 9 // 這是執行乘法運算的內部塊,
10 // 並減少「n」的值
11 var block = Expression.Block(
12     Expression.Assign(result,
13         Expression.Multiply(result, nArgument)),
14     Expression.PostDecrementAssign(nArgument)
15 );
16 
17 // 建立一個方法體
18 BlockExpression body = Expression.Block(
19     new[] { result },
20     initializeResult,
21     Expression.Loop(
22         Expression.IfThenElse(
23             Expression.GreaterThan(nArgument, Expression.Constant(1)),
24             block,
25             Expression.Break(label, result)
26         ),
27         label
28     )
29 );
檢查 API

表達式樹 API 在 .NET Core 中較難導航,但不要緊。 它們的用途至關複雜:編寫在運行時生成代碼的代碼。 它們必須具備複雜的結構,才能在支持 C# 語言中提供的全部控件結構和儘量減少 API 表面積之間保持平衡。 這種平衡意味着許多控件結構不是由其 C# 構造表示,而是由表示基礎邏輯的構造表示,這些基礎邏輯由編譯器從這些較高級別的構造生成。

另外,此時存在一些不能經過使用 Expression 類方法直接生成的 C# 表達式。 通常來講,這些將是在 C# 5 和 C# 6 中添加的最新運算符和表達式。 (例如,沒法生成 async 表達式,而且沒法直接建立新 ?. 運算符。)

相關文章
相關標籤/搜索