C# 編譯程式已建立到目前為止您已看到的所有運算式樹狀結構。 您已建立一個 Lambda 運算式,並將其指派給類型為 Expression<Func<T>>
或相似類型的變數。 在許多情況下,您會在運行時間在記憶體中建置表達式。
表達式樹狀結構是不可變的。 不可變意味著您必須從樹葉開始構建整棵樹到樹根。 用來建立表示式樹的 API 會反映這個事實:用來建立節點的方法會將所有子節點作為參數。 讓我們逐步解說一些範例來示範技術。
建立節點
首先,您會從您在下列各節中使用的加法表達式開始:
Expression<Func<int>> sum = () => 1 + 2;
若要建構該運算式樹狀結構,您必須先建構分葉節點。 分葉節點是常數。 使用Constant方法來建立節點。
var one = Expression.Constant(1, typeof(int));
var two = Expression.Constant(2, typeof(int));
接下來,建置加法表達式:
var addition = Expression.Add(one, two);
建置加法表示式之後,您會建立 Lambda 表達式:
var lambda = Expression.Lambda(addition);
此 Lambda 運算式不包含任何自變數。 在本節稍後,您將瞭解如何將自變數對應至參數,並建置更複雜的表達式。
對於類似此表達式的表示式,您可以將所有呼叫合併成單一語句:
var lambda2 = Expression.Lambda(
Expression.Add(
Expression.Constant(1, typeof(int)),
Expression.Constant(2, typeof(int))
)
);
建一棵樹
上一節顯示在記憶體中建置表達式樹狀結構的基本概念。 更複雜的樹狀結構通常表示更多節點類型,以及樹狀結構中的更多節點。 讓我們再執行一個範例,並顯示您在建立表達式樹狀架構時通常會建置的兩個節點類型:自變數節點和方法呼叫節點。 讓我們建置表達式樹狀結構來建立此表達式:
Expression<Func<double, double, double>> distanceCalc =
(x, y) => Math.Sqrt(x * x + y * y);
首先,為 x
和 y
建立參數表達式:
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) }) ?? throw new InvalidOperationException("Math.Sqrt not found!");
var distance = Expression.Call(sqrtMethod, sum);
如果找不到 方法,呼叫 GetMethod
可能會傳回 null
。 很可能是因為您拼錯了方法名稱。 否則,這可能表示未載入必要的組件。 最後,您會將方法呼叫放入 Lambda 運算式中,並確定定義 Lambda 表達式的自變數:
var distanceLambda = Expression.Lambda(
distance,
xParameter,
yParameter);
在這個更複雜的範例中,您會看到一些額外的技術,這些技術通常需要用來建立表達式樹。
首先,您需要先建立代表參數或局部變數的物件,再使用它們。 建立這些物件之後,您就可以在表達式樹狀目錄中視需要使用這些物件。
其次,您必須使用反映 API 的子集來建立 System.Reflection.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。相反地,你需要建置一個包含條件測試的迴圈,以及一個標籤目標以便能夠根據條件跳出迴圈。
var nArgument = Expression.Parameter(typeof(int), "n");
var result = Expression.Variable(typeof(int), "result");
// Creating a label that represents the return value
LabelTarget label = Expression.Label(typeof(int));
var initializeResult = Expression.Assign(result, Expression.Constant(1));
// This is the inner block that performs the multiplication,
// and decrements the value of 'n'
var block = Expression.Block(
Expression.Assign(result,
Expression.Multiply(result, nArgument)),
Expression.PostDecrementAssign(nArgument)
);
// Creating a method body.
BlockExpression body = Expression.Block(
new[] { result },
initializeResult,
Expression.Loop(
Expression.IfThenElse(
Expression.GreaterThan(nArgument, Expression.Constant(1)),
block,
Expression.Break(label, result)
),
label
)
);
建置因數函式表達式樹狀結構的程式代碼相當長、更複雜,而且它充滿了卷標和 break 語句,以及您想要在我們的日常編碼工作中避免的其他元素。
在本節中,您會撰寫程式代碼來瀏覽此表達式樹狀結構中的每個節點,並寫出此範例中建立之節點的相關信息。 您可以在 dotnet/docs GitHub 存放庫 檢視或下載範例程式代碼 。 親自嘗試建置和執行範例。
將程式碼構造對應到表達式
下列程式代碼範例示範表達式樹狀結構,此樹狀結構會使用 API 來表示 Lambda 運算式 num => num < 5
。
// Manually build the expression tree for
// the lambda expression num => num < 5.
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 });
表達式樹狀架構 API 也支援指派和控制流程運算式,例如迴圈、條件式區塊和 try-catch
區塊。 藉由使用 API,您可以建立比 C# 編譯程式可從 Lambda 運算式建立的運算式樹狀結構更複雜。 下列範例示範如何建立表達式樹狀結構,以計算數位的乘積。
// Creating a parameter expression.
ParameterExpression value = Expression.Parameter(typeof(int), "value");
// Creating an expression to hold a local variable.
ParameterExpression result = Expression.Parameter(typeof(int), "result");
// Creating a label to jump to from a loop.
LabelTarget label = Expression.Label(typeof(int));
// Creating a method body.
BlockExpression block = Expression.Block(
// Adding a local variable.
new[] { result },
// Assigning a constant to a local variable: result = 1
Expression.Assign(result, Expression.Constant(1)),
// Adding a loop.
Expression.Loop(
// Adding a conditional block into the loop.
Expression.IfThenElse(
// Condition: value > 1
Expression.GreaterThan(value, Expression.Constant(1)),
// If true: result *= value --
Expression.MultiplyAssign(result,
Expression.PostDecrementAssign(value)),
// If false, exit the loop and go to the label.
Expression.Break(label, result)
),
// Label to jump to.
label
)
);
// Compile and execute an expression tree.
int factorial = Expression.Lambda<Func<int, int>>(block, value).Compile()(5);
Console.WriteLine(factorial);
// Prints 120.
如需詳細資訊,請參閱 在Visual Studio 2010中使用表達式樹狀架構產生動態方法,這也適用於較新版本的Visual Studio。