在 .NET 開發中,LINQ Lambda 表達式是一項強大且核心的功能,但許多開發者初次接觸時往往感到困惑:這段代碼究竟代表什麼?它是如何運作的?底層原理又是什麼?本文將通過逐層拆解,帶你深入理解 Lambda 表達式的本質——表達式樹。
考慮以下代碼片段:
Expression<Func<string, bool>> expr = o => o.Length > 10;
乍看之下,這似乎只是一個委托聲明,但實際上它構建了一個 Expression<TDelegate> 物件。讓我們以等號為界,分為左右兩部分進行分析。
Expression<Func<string, bool>> expr 表明 expr 是一個 LINQ Lambda 表達式,其類型符合委托簽名 bool DelegateName(string obj)。這裡的關鍵在於 Expression<T>,它告訴編譯器:請不要將右側編譯為可執行的 IL 代碼(即委托),而是將其轉換為數據結構——表達式樹。
o => o.Length > 10 中的 => 是 Lambda 運算符,讀作「轉到」。將其視為一個匿名方法:
o 是輸入參數聲明。o.Length > 10 是方法體。若用傳統寫法等價表示:
bool MethodName(string o)
{
return o.Length > 10;
}
這是最關鍵的認知點:expr 絕不是該匿名方法的返回值,而是該方法內部所有表達式的 System.Linq.Expressions.Expression 形式。
換句話說,編譯器將函數體拆解為一個個獨立的表達式單元(如參數訪問、屬性調用、常量、二元運算),每個單元都是 Expression 的派生類(ParameterExpression、MemberExpression、ConstantExpression、BinaryExpression 等)。這些表達式之間存在層級關係,最終形成一棵表達式樹。
開發者無需手動構建樹結構,C# 編譯器在編譯階段會自動完成轉換。通過反編譯工具(如 Reflector)查看上述代碼編譯後的樣貌:
Expression<Func<string, bool>> expr;
// 1. 創建參數表達式
ParameterExpression paramExpr = Expression.Parameter(typeof(string), "o");
// 2. 獲取 Length 屬性的 PropertyInfo
PropertyInfo propInfo = typeof(string).GetProperty("Length", BindingFlags.Instance | BindingFlags.Public);
// 3. 創建訪問屬性的表達式
MemberExpression memberExpr = Expression.Property(paramExpr, propInfo);
// 4. 創建常量 10 的表達式
ConstantExpression constExpr = Expression.Constant(10, typeof(int));
// 5. 創建「大於」二元運算表達式
BinaryExpression greaterThanExpr = Expression.GreaterThan(memberExpr, constExpr);
// 6. 組裝成 Lambda 表達式樹
expr = Expression.Lambda<Func<string, bool>>(greaterThanExpr, paramExpr);
這過程雖然繁瑣,但完全由編譯器自動完成。只有在需要動態構建查詢(如根據用戶輸入動態生成 Where 條件)時,開發者才需手動使用 System.Linq.Expressions API 構建樹。
面對 LINQ Lambda 表達式,請牢記一條核心原則:
不要把表達式看作語句的運算結果,而應視為「語句本身」——即將代碼作為數據對象來處理。
語句之間通過表達式樹關聯,從源代碼到表達式樹的轉換由編譯器自動完成,無需人工干預。這正是 LINQ 能夠實現 IQueryable、跨進程執行(如 LINQ to SQL、Entity Framework)的基石:將「意圖」序列化為樹結構,再由提供者翻譯為目標語言(SQL、HTTP 請求等)。