【问题标题】:How closure in c# works when using lambda expressions?使用 lambda 表达式时,c# 中的闭包如何工作?
【发布时间】:2014-02-19 19:46:23
【问题描述】:

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

他们说下面的代码:

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

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

0223557799

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

我的错误在哪里?

如果答案能解释闭包是如何工作的,它如何持有指向特定 int 的“指针”,在这种特定情况下会生成什么代码,那将非常有帮助?

【问题讨论】:

  • 在 C# i,而不是循环体中的当前值。我相信 C#5 会改变这种行为并改为捕获当前值,因此 i 将始终具有不同的值。
  • 您希望捕获i 的哪个值?试试for (int i=0;i&lt;10;i++){int j=i;new Thread(()=&gt;Console.Write(j)).Start();}
  • @Lee 这是错误的。闭包语义没有任何改变。在每一个 C# 版本中,闭包都关闭了变量,而不是(当然,闭包在 1.0 中根本不存在,但是每个有闭包的版本都有相同的语义)。
  • @Lee - C# 5 中的变化特别针对 foreach 迭代变量。 for 完全没有改变。

标签: c# lambda


【解决方案1】:

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

当您看到编译器将闭包转换成什么时,很容易看出这是如何发生的。它会创造出与此类似的道德:

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(尽管顺序是任意的,因为线程可以根据操作系统的需要进行调度)。

【讨论】:

【解决方案2】:

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

发生这种情况是因为在线程有时间启动之前,循环已经更改了 i 中的任何值。

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

【讨论】:

    【解决方案3】:

    Console.Write(i) 中的i 在该语句即将执行时被正确评估。一旦线程完全创建并开始运行并到达该代码,该语句将被执行。到那时,循环已经向前移动了几次,因此 i 到那时可以是任何值。与常规函数不同,闭包对定义它的函数的局部变量具有可见性(是什么让它们变得有用,以及让自己大吃一惊的方法)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-03-24
      • 1970-01-01
      • 1970-01-01
      • 2010-10-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多