在循环控制结构内使用匿名方法的局部变量的用法

  当处理循环控制结构时将局部变量封装入类的数据成员有着有趣但危险的一面,让我们看看下面代码:

public class Program
{
 public delegate void MyDelegate();
 public static void Main(string[] args)
 {
  MyDelegate d = null;
  for (int i = 1; i <= 5; i++)
  {
   MyDelegate tempD = delegate
   {
    Console.WriteLine(i);
   };
   d += tempD;
  }
  d();
 }
}

  上面的代码运行时将会有什么输出呢?我们的意图是捕获在我们的匿名方法中的循环计数变量''i''并显示之。我们预期的输出应该如下所示:

  1
  2
  3
  4
  5

  但是如果你运行上面的代码,输出将是如下所示:
 
  6
  6
  6
  6
  6

  如果我们仔细回忆我们关于匿名方法的内部工作机制的知识,我提到:在匿名方法中被捕获的任何局部变量将会被该作用域的一个新的已创建内部类的实例数据成员替代。对于循环控制变量,作用域是包含了for循环的作用域,这就是上面的简单代码所示的main方法体。因此当该代码编译时,C#编译器生成创建了内部类的实例的代码,包装了匿名方法和循环计数变量,在for循环的外部。并且该内部类的实例的数据成员,代表了循环计数变量,将被用来替代用于for循环而且也在匿名方法中使用的原始循环计数变量。因此来自内部类的相同实例的数据成员被用于for循环并且也用在包装匿名方法的实例方法中。作为循环完成时的结果,实例数据成员会增加六次。这里有一个需要注意的重要地方:尽管这个循环在五次迭代后结束,在它跳出循环控制结构时循环计数变量被增加了六次。既然该循环控制变量是一个实例数据成员,第六次增加触发了已由循环计数变量提供的循环结束条件。既然相同实例的一个方法被用做匿名方法的委托处理器,在委托结束时被调用,所有委托的实例将被指向相同实例,同时将为数据成员显示相同值,就是6。这就是我在本节开始已提到过的有危险影响的一面。

  为了克服这个问题并获得预期的结果,匿名方法应该在for循环的作用域中捕获一个局部变量,它将有与循环计数变量的相同的值。这可以通过如下修改示例代码获得:

public class Program
{
 public delegate void MyDelegate();
 public static void Main(string[] args)
 {
  MyDelegate d = null;
  for (int i = 1; i <= 5; i++)
  {
   int k = i;
   MyDelegate tempD = delegate
   {
    Console.WriteLine(k);
   };
   d += tempD;
  }
  d();
 }
}

  在你运行上面的代码示例时,将会获得预期的输出,也就是:

  1
  2
  3
  4
  5

  原因就是,C#编译器将为for循环的每次迭代而包装局部变量''k''的内部类创建 实例。同时包装了每个循环迭代的实例上的匿名方法的这个方法被用做一个委托处理器。