关于C#封闭的另一个问题

时间:2013-02-18 00:11:52

标签: c# closures

我理解在C#闭包中提出并回答或讨论了很多问题。但请在我的小实验上给我一点时间......

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Timers;

namespace ConsoleApplication1
{
  class Program
  {
    static void Main(string[] args)
    {
      using (var timer = new Timer(500))
      {
        timer.AutoReset = false;

        GetFunc2(timer, 0);
        // GetFunc3(timer, 0);

        timer.Start();

        Console.ReadLine();
      }
    }

    static void GetFunc2(Timer timer, int i)
    {
      for (; i < 5; ++i)
      {
        timer.Elapsed += (obj, e) =>
          {
            Console.WriteLine(i);
          };
      }
    }

    static void GetFunc3(Timer timer, int i)
    {
      timer.Elapsed += (obj, e) =>
      {
        Console.WriteLine(i++);
      };

      timer.Elapsed += (obj, e) =>
      {
        Console.WriteLine(i++);
      };

      timer.Elapsed += (obj, e) =>
      {
        Console.WriteLine(i++);
      };

      timer.Elapsed += (obj, e) =>
      {
        Console.WriteLine(i++);
      };

      timer.Elapsed += (obj, e) =>
      {
        Console.WriteLine(i++);
      };
    }
  }
}

通过单独调用GetFunc2中的GetFunc3Main,我们可以看到输出不同,尽管GetFun3看起来只是GetFunc2的简单扩展。谁知道为什么?我认为ildasm可以揭示不同的生成代码,但我确实想知道原因。在VS2012 Pro上测试,.net 4.5。

3 个答案:

答案 0 :(得分:3)

这个覆盖的次数是压倒性的......

我不会再讨论讨论,但请看Eric Lippert的答案和其他人的答案:

https://stackoverflow.com/a/8899347/1517578

对这些答案的评论也很有趣。

另外,有关它的Eric博客文章的链接在这里:http://blogs.msdn.com/b/ericlippert/archive/2009/11/12/closing-over-the-loop-variable-considered-harmful.aspx

基本上,您需要将关闭的变量复制到局部变量中:

static void GetFunc2(Timer timer, int i)
{
  for (; i < 5; ++i)
  {
    int i2 = i; // STORE IT
    timer.Elapsed += (obj, e) =>
      {
        Console.WriteLine(i2); // USE THE NEWLY STORED VERSION
      };
  }
}

这导致:

0
1
2
3
4

..正如所料。

答案 1 :(得分:0)

AFAIK闭包始终包含引用到“parent”方法的局部变量。所以不同之处在于,在GetFunc2中实际调用这些函数之前,您正在递增值。所以在通话时,已经有5的价值。在GetFunc3中,您在提升事件时递增值,因此它将计数。

答案 2 :(得分:0)

如果GetFunc2在调用(执行)匿名方法之前增加i,那么当您调用您看到的方法时,i已经递增。

如果是GetFunc3,则在调用每个匿名方法之前不会增加i,因此它从零开始并随着每次调用而不断增加。