初接觸 LINQ 者常覺得表達式樹難懂,實則不然。只要具備基本 LINQ 知識即可輕鬆理解。表達式樹提供「可執行程式碼 → 資料結構」轉換,若需在執行前修改/轉換程式碼 (如 LINQ 查詢轉 SQL) 極具價值。
簡單 Lambda 運算式:
Func<int, int, int> function = (a, b) => a + b;
三部分:委托類型宣告、等號、Lambda 運算式。function 指向原生可執行程式碼,等同:
public int function(int a, int b) { return a + b; }
引入 System.Linq.Expressions:
Expression<Func<int, int, int>> expression = (a, b) => a + b;
expression 非可執行程式碼,而是名為表達式樹的資料結構。
上半部顯示 Lambda 運算式,下半部 TreeView 顯示組成部分。
Expression<TDelegate> 四大屬性:
| 屬性 | 說明 |
|---|---|
| Body | 取得表達式主體 |
| Parameters | 取得 Lambda 參數 |
| NodeType | 節點類型 (45 種 ExpressionType,如 Add、LessThan、Constant 等) |
| Type | 表達式靜態類型 (此例為 Func<int, int, int>) |
// 參數名稱
Console.WriteLine("參數1: {0}, 參數2: {1}", expression.Parameters[0], expression.Parameters[1]);
// 輸出: 參數1: a, 參數2: b
// 主體結構
BinaryExpression body = (BinaryExpression)expression.Body;
ParameterExpression left = (ParameterExpression)body.Left;
ParameterExpression right = (ParameterExpression)body.Right;
Console.WriteLine(expression.Body); // (a + b)
Console.WriteLine("左邊: {0}\n節點類型: {1}\n右邊: {2}\n類型: {3}", left.Name, body.NodeType, right.Name, body.Type);
輸出:
(a + b)
左邊: a
節點類型: Add
右邊: b
類型: System.Int32
int result = expression.Compile()(3, 5);
Console.WriteLine(result); // 8
Compile() 將表達式樹轉回可執行委托,結果與原始 Lambda 相同。
標準 LINQ to SQL 查詢:
var query = from c in db.Customers
where c.City == "Nantes"
select new { c.City, c.CompanyName };
query 為 IQueryable 類型,定義:
public interface IQueryable : IEnumerable
{
Type ElementType { get; }
Expression Expression { get; }
IQueryProvider Provider { get; }
}
包含 Expression 屬性 (基類為 Expression<T>),實例擁有相關表達式樹,等同查詢運算式的可執行程式碼資料結構。
LINQ to SQL 查詢不在 C# 程式執行,而是轉 SQL、透過網路發送、在資料庫伺服器執行。原始程式碼從未在程式中執行:
// 實際轉換為 SQL 執行:
SELECT [t0].[City], [t0].[CompanyName]
FROM [dbo].[Customers] AS [t0]
WHERE [t0].[City] = @p0
從程式碼 → 字串 (SQL) 傳遞給其他程式。解析表達式樹資料結構遠比解析 IL/原生程式碼容易。編譯器分析表達式樹各部分,決定最佳 SQL 生成策略,理論上可考慮網路狀況、資料庫負載等因素。
LINQ to Objects 回傳 IEnumerable<T>,不含 Expression 屬性,極少使用表達式樹。差異在於:
IEnumerable<T>,直接轉 IL 執行IQueryable<T> + 表達式樹,轉換為字串 (SQL、Web Service 呼叫等)如 LINQ to Amazon 將查詢轉 Web Service 呼叫,同樣使用 IQueryable<T>。程式內執行 (如反射 API 查詢) 仍用 IEnumerable<T>。
原文出處:Charlie Calvert《Expression Tree Basics》