防止Lambda的各种坑爹(二)
2.循环内的被捕获的变量。
首先看一段代码:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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#代码我想是这样的
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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。如下代码将证实这一点:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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表达式的时候需要注意的地方。