自 .NET Framework 3.5 引入 LINQ 以來,其強大且優雅的查詢語法深受開發者喜愛。隨著 LINQ to NHibernate、LINQ to Google 等 Provider 層出不窮,「一切皆 LINQ」的趨勢愈發明顯。LINQ 本身具備優異的擴展性,讓我們能輕鬆打造專屬的 LINQ Provider。本文為系列首篇,將深入剖析表達式樹的核心知識,為後續實作奠基。
表達式樹本質上是一種抽象語法樹(AST)或資料結構,透過解析它可實現特定功能。最簡單的構造方式是使用 Lambda 表達式賦值給 Expression<TDelegate> 類型變數:
Expression<Func<int, int, int>> expression = (a, b) => a * b + 2;
編譯器會生成表達式樹指令,將程式碼以資料結構形式呈現。上述 Lambda 表達式對應的樹狀結構如下:

每個節點代表一種表達式:ParameterExpression(參數)、ConstantExpression(常數)、BinaryExpression(二元運算)等。Visual Studio 內建的 Expression Tree Visualizer(可從 MSDN Code Gallery 的 LINQ Sample 取得)能直觀展示樹結構:


所有表達式類別皆繼承自抽象基類 Expression,泛型 Expression<TDelegate> 則繼承 LambdaExpression。Expression 類別提供大量工廠方法建立各類表達式物件,例如 Add() 建立不檢查溢位的加法 BinaryExpression,Lambda() 建立 LambdaExpression。完整方法請參閱 MSDN。
除了透過 Lambda 自動生成,也可手動組合表達式樹:
static void Main(string[] args)
{
ParameterExpression paraLeft = Expression.Parameter(typeof(int), "a");
ParameterExpression paraRight = Expression.Parameter(typeof(int), "b");
BinaryExpression binaryLeft = Expression.Multiply(paraLeft, paraRight);
ConstantExpression conRight = Expression.Constant(2, typeof(int));
BinaryExpression binaryBody = Expression.Add(binaryLeft, conRight);
LambdaExpression lambda = Expression.Lambda<Func<int, int, int>>(binaryBody, paraLeft, paraRight);
Console.WriteLine(lambda.ToString());
Console.Read();
}
輸出結果與自動生成完全一致:

Func<int,int,int>)Expression Tree Visualizer 可檢視這些屬性:

常見困惑:Func<int,int,int> lambda = (a,b) => a + b * 2; 與 Expression<Func<int,int,int>> expression = (a,b) => a + b * 2; 差異何在?
前者編譯為直接可執行的 IL 程式碼;後者編譯器會分析 Lambda 並生成對應的表達式樹結構(等同手動構造的 IL)。這差異是理解表達式樹「可被解析、轉換」特性的關鍵。
呼叫 Compile() 即可產生可執行委托:
Expression<Func<int, int, int>> lambda = ...;
Func<int, int, int> myLambda = lambda.Compile();
int result = myLambda(2, 3); // 輸出 8

內部由 ExpressionCompiler 處理(非傳統編譯器編譯)。僅 LambdaExpression 或 Expression<TDelegate> 可直接執行;其他類型需先用 Lambda() 包裝。
表達式樹是不可變的,「修改」實為建立副本。.NET 提供內部 ExpressionVisitor,MSDN 公開其實作供繼承。例如將 (a,b) => a + b * 2 轉為 (a,b) => a - (b * 2):
public class OperationsVisitor : ExpressionVisitor
{
public Expression Modify(Expression expression) => Visit(expression);
protected override Expression VisitBinary(BinaryExpression b)
{
if (b.NodeType == ExpressionType.Add)
{
Expression left = Visit(b.Left);
Expression right = Visit(b.Right);
return Expression.Subtract(left, right);
}
return base.VisitBinary(b);
}
}
使用方式:
var lambda = (a, b) => a + b * 2;
var visitor = new OperationsVisitor();
Expression modified = visitor.Modify(lambda);
Console.WriteLine(modified.ToString()); // 輸出 (a, b) => a - (b * 2)

以 LINQ to SQL 為例:查詢運算式封裝為 IQueryable,其核心屬性 Expression(表達式樹)與 Provider(查詢提供者)決定翻譯方式。這架構賦予極高彈性,讓我們能針對任意資料源編寫 Provider,實現「統一程式模型、多元資料存取」。


注意:LINQ to Objects 不轉換為表達式樹,直接在記憶體執行。
本文詳細介紹表達式樹的構造、屬性、執行、訪問修改及其在 LINQ 架構中的核心地位,為打造自訂 LINQ Provider 鋪路。下篇將實戰開發 Provider。