使用lambda表达式时c#中的闭包是如何工作的?

在以下教程中: http : //www.albahari.com/threading/

他们说以下代码:

for (int i = 0; i  Console.Write (i)).Start(); 

是非确定性的,可以产生以下答案:

0223557799

我认为当使用lambda表达式时,编译器会创建某种匿名类,通过在捕获类中创建类似它们的成员来捕获正在使用的变量。 但i是价值类型,所以我认为他应该被价值复制。

我的错在哪里?

如果答案将解释闭包是如何工作的,它如何保存特定int的“指针”,在这个特定情况下生成了什么代码,将会非常有用?

这里的关键点是闭包关闭变量而不是值 。 因此,在您关闭它时给定变量的值是无关紧要的。 重要的是调用匿名方法时该变量的值。

当你看到编译器将闭包转换成什么时,这很容易看出来。 它会创造一些与此类似的道德观点:

 public class ClosureClass1 { public int i; public void AnonyousMethod1() { Console.WriteLine(i); } } static void Main(string[] args) { ClosureClass1 closure1 = new ClosureClass1(); for (closure1.i = 0; closure1.i < 10; closure1.i++) new Thread(closure1.AnonyousMethod1).Start(); } 

所以在这里我们可以更清楚地看到发生了什么。 该变量有一个副本,该变量现在已被提升为新类的字段,而不是本地变量。 现在修改局部变量的任何地方都会修改此实例的字段。 我们现在可以看到为什么你的代码打印它的作用。 在启动新线程之后,但在它实际执行之前,主线程中的for循环将返回并在闭包中递增变量。 闭包尚未读取的变量。

要产生所需的结果,您需要做的是确保不是让循环的每个迭代都关闭单个变量,而是每个迭代都需要一个它们关闭的变量:

 for (int i = 0; i < 10; i++) { int copy = i; new Thread(() => Console.WriteLine(copy)); } 

现在copy变量在关闭之后永远不会改变,并且我们的程序将打印出0-9(尽管以任意顺序,因为线程可以按照操作系统的要求进行调度)。

正如Albahari所说,尽管传递的参数是值类型,但每个线程都会捕获memory location从而导致意外结果。

发生这种情况是因为在Thread有任何时间开始之前,循环已经改变了i内部的任何值。

为了避免这种情况,你应该像Albahari所说的那样使用temp变量,或者只在知道变量不会改变时使用它。

iConsole.Write(i)中即将执行该语句时被评估。 一旦线程完全创建并开始运行并获得该代码,该语句将被执行。 到那时,循环向前移动了几次,因此i可以成为任何值。 与常规函数不同,闭包可以看到定义它的函数的局部变量(使它们有用的原因,以及用脚射击自己的方式)。