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

时间:2014-02-19 19:46:24

标签: c# lambda

进入以下教程:http://www.albahari.com/threading/

他们说以下代码:

for (int i = 0; i < 10; i++)
  new Thread (() => Console.Write (i)).Start();

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

  

0223557799

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

我的错误在哪里?

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

3 个答案:

答案 0 :(得分:8)

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

当您看到编译器将闭包转换为什么时,这很容易看到。它会创造一些与此类似的东西:

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(尽管以任意顺序,因为线程可以按照OS的要求进行调度)。

答案 1 :(得分:2)

Albahari所述,虽然传递参数是值类型,但每个线程都会捕获memory location,从而导致意外结果。

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

为避免这种情况,您应该使用temp变量作为Albahari声明,或者仅在您知道变量不会更改时才使用它。

答案 2 :(得分:1)

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