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
}
for 迴圈中 i 只有一個記憶體位址,每次迴圈只更新值。i 的位址,真正執行時才取值。迴圈結束後 i 變為 3,所有 Lambda 皆指向同一個 i。
for (int i = 0; i < 3; i++) {
int temp = i; // 每次迴圈重新分配記憶體
list.Add(() => temp); // 捕獲獨立的 temp
}
// 輸出 0, 1, 2
這個 temp 稱為 Lifting(中文常譯「提升」或「墊腳石」),讓每次迴圈捕獲獨立變數實體。
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 值)。
foreach (string keyWord in keyWords) {
string temp = keyWord; // Lifting
entitys = entitys.Where(e => e.Text.Contains(temp));
}
或改用 for 迴圈並引入暫存變數。foreach 迴圈變數在 C# 5.0 前皆為單一實體,極易踩坑。
for、foreach)在舊版 C# 中為單一實體,需手動 Lifting。foreach 迴圈變數每次迴圈重新建立,但 for 迴圈仍需注意。