防止Lambda的各種坑爹(二)


  2.循環內的被捕獲的變量。

  首先看一段代碼:

View Code
 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#代碼我想是這樣的

View Code
 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。如下代碼將證實這一點:

View Code
 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表達式的時候需要注意的地方。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM