首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> C# >> 關於C#

C# 3.0 Lambda 表達式進階陷阱:變數捕獲與 Lifting 完整解析

     編輯:關於C#

Lambda 表達式中的 Lifting(變數提升/捕獲)

Lambda 由匿名方法演化而來。先看一個經典陷阱:


List> list = new List>();
for (int i = 0; i < 3; i++) {
    list.Add(() => i); // 捕獲迴圈變數 i
}
foreach (var item in list) {
    Console.WriteLine(item()); // 預期 0,1,2 實際全印 3
}

為什麼全是 3?

  1. 單一變數實體:for 迴圈中 i 只有一個記憶體位址,每次迴圈只更新值。
  2. 捕獲的是變數參考而非值:Lambda 建構時捕獲 i 的位址,真正執行時才取值。迴圈結束後 i 變為 3,所有 Lambda 皆指向同一個 i。

解決方案:引入區域暫存變數(Lifting)


for (int i = 0; i < 3; i++) {
    int temp = i; // 每次迴圈重新分配記憶體
    list.Add(() => temp); // 捕獲獨立的 temp
}
// 輸出 0, 1, 2

這個 temp 稱為 Lifting(中文常譯「提升」或「墊腳石」),讓每次迴圈捕獲獨立變數實體。

Lifting 在 LINQ to SQL 的實戰影響

Lambda 大量用於 LINQ to SQL,若不加 Lifting 會產生錯誤 SQL:


string[] keyWords = { "111", "222", "333", "444" };
var entitys = from e in ctx.Entity select e;
foreach (string keyWord in keyWords) {
    entitys = entitys.Where(e => e.Text.Contains(keyWord)); // 陷阱!
}
var q = entitys.ToList();

實際 SQL 僅傳入 %444%(最後一個 keyWord 值)。

修正:加入 Lifting


foreach (string keyWord in keyWords) {
    string temp = keyWord; // Lifting
    entitys = entitys.Where(e => e.Text.Contains(temp));
}

或改用 for 迴圈並引入暫存變數。foreach 迴圈變數在 C# 5.0 前皆為單一實體,極易踩坑。

核心觀念總結

  • Lambda 捕獲的是變數,而非當下的值。
  • 迴圈變數(for、foreach)在舊版 C# 中為單一實體,需手動 Lifting。
  • C# 5.0 起 foreach 迴圈變數每次迴圈重新建立,但 for 迴圈仍需注意。
  • LINQ to SQL 延遲執行特性使此問題更隱蔽,必須養成 Lifting 習慣。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved