防止Lambda的各种坑爹(一)
1.奇怪的被捕获的变量:
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 TestLambda 8 { 9 class Program 10 { 11 static void Main(string[] args) 12 { 13 Action action = CreateAction(); 14 action(); 15 action(); 16 action(); 17 Console.Read(); 18 } 19 20 static Action CreateAction() 21 { 22 int value = 0; 23 Action action = () => Console.Write(value++); 24 return action; 25 } 26 } 27 }
你觉得上面的代码会输出什么呢?我们一般会认为,由于静态方法有自己线程栈,value变量会在对应的栈上分配空间,所以一旦方法CreateAction执行完毕,CreateAction对应的线程栈被销毁,value变量也会随之消失...但是令人惊讶的是,第二次和第三次调用action委托的时候,value变量似乎依然保留这上次调用后的值。所以我们认为输出的结果是000。可是运行代码,输出的结果却是 012。
奇怪吗?秘密就在于我们之前的那个假设上->value是真的分配在自己的线程栈上的吗?答案是否定的。事件上呢,编译器会为我们创建一个额外的类来存储变量。CreateAction拥有对该类的一个实例的引用,所以CreateAction可以访问到变量value。同时Action委托也有对该实例的引用。这样的话,除非Action委托和CreateAction方法同时被销毁的时候,变量value所在的实例才会被销毁。通过ILDASM,可以清楚的看到编译器所产生的类<>c_DisplayClass1。
同时在CreateAction方法中可以找到对<>c_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 TestLambda 8 { 9 class Program 10 { 11 static void Main(string[] args) 12 { 13 Action action = CreateAction(); 14 action(); 15 action(); 16 action(); 17 18 var a = DynamicCreateAction(); 19 a(); 20 a(); 21 a(); 22 23 Console.Read(); 24 } 25 26 static Action CreateAction() 27 { 28 int value = 0; 29 Action action = () => Console.Write(value++); 30 return action; 31 } 32 33 static Action DynamicCreateAction() 34 { 35 DisplayClass d = new DisplayClass(); 36 37 Action action = new Action(d.Print); 38 return action; 39 } 40 41 class DisplayClass 42 { 43 public int Value { get; set; } 44 45 public void Print() 46 { 47 Console.Write(Value++); 48 } 49 } 50 51 } 52 53 }
OK,我想你现在明白了,"局部变量“不一定是局部的了吧。