2.循環內的被捕獲的變量。
首先看一段代碼:

1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 7 namespace Lambda2 8 { 9 class Program 10 { 11 static void Main(string[] args) 12 { 13 List<Action> list = new List<Action>(); 14 for (int i = 0; i < 3; i++) 15 { 16 int current = i; 17 list.Add(() => 18 { 19 Console.Write(current); 20 current++; 21 }); 22 } 23 24 foreach (var item in list) 25 { 26 item(); 27 } 28 list.First()(); 29 list.First()(); 30 31 Console.Read(); 32 } 33 } 34 }
你能猜出輸出的是什么嗎?如果你的答案是01212,那么恭喜你,你的答案是正確的。這里可以看出:當在Lambda中捕獲一個變量時,被捕獲的是變量的實例。也就是說,循環第一次捕獲的變量將有別與循環第二次捕獲的變量,就像有3個current變量一樣,全部叫做current,他們一個接一個的創建。
代碼會創建3個不同的委托---每次循環都會創建一個,添加到一個List集合中。現在,由於current變量是在循環內聲明的,所以每次循環迭代。他都會被創建。這樣每次委托捕獲到的都是不同的current變量的值。所以一次調用每個委托。輸出的結果依次是0 1 2。然后我們在執行2個第一個委托,由於在執行了current++,所以依次再輸出 1 2。
我想你一定不奇怪為什么每次的current變量的值不同,因為這個看上次似乎是理所當然的。是這樣嗎?我們通過ILDASM查看對應生成的IL代碼:發現編譯器依舊為我們生成的了一個類DisPlayClass1,這個類包裝了current變量和委托包裝的方法 <Main>b__0:void: Console.Write(current);current++。
同時可以看到new DisplayClass1的位置在循環內部
對應的C#代碼我想是這樣的

1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 7 namespace Lambda2 8 { 9 class Program 10 { 11 private class DisplayClass1 12 { 13 public int current; 14 15 public void F() 16 { 17 Console.Write(current); 18 current++; 19 } 20 } 21 22 static void Main(string[] args) 23 { 24 25 List<Action> list = new List<Action>(); 26 for (int i = 0; i < 3; i++) 27 { 28 DisplayClass1 d = new DisplayClass1(); 29 d.current = i; 30 list.Add(d.F); 31 } 32 33 foreach (var item in list) 34 { 35 item(); 36 } 37 list.First()(); 38 list.First()(); 39 40 Console.Read(); 41 } 42 } 43 }
好了,這樣的話就有了一個新問題,應該思考的一點是,如果我們移除current變量,直接用for循環中的i的代替的話,那么會發生什么呢?在這種情況下,所以的循環內的委托共享的是一個變量i。輸出的將是3 4 5 6 7。之所以這樣,是因為在循環結束時,i的值是3(同時要注意的是,委托內的i++不會現在執行)。之后的每次調用委托都會使i++,每個委托都是調用的同一個變量i。如下代碼將證實這一點:

1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 7 namespace Lambda2 8 { 9 class Program 10 { 11 static void Main(string[] args) 12 { 13 14 List<Action> list = new List<Action>(); 15 for (int i = 0; i < 3; i++) 16 { 17 list.Add(() => 18 { 19 Console.Write(i); 20 i++; 21 }); 22 } 23 24 foreach (var item in list) 25 { 26 item(); 27 } 28 list.First()(); 29 list.First()(); 30 31 Console.Read(); 32 } 33 } 34 }
同時看到對應的IL代碼,new DisplayClass1的位置在循環外部
好了,這個提醒我們以后在循環內部使用Lambda表達式的時候需要注意的地方。