C#闭包,为什么循环变量通过引用捕获?

在这个例子中,我试图通过值传递,但是传递了引用。

for (int i = 0; i  new PhoneJobTest(i); t.Start(); } 

这样可以解决:

  for (int i = 0; i  new PhoneJobTest(jobNum); t.Start(); } 

这是怎么回事? 为什么原始示例传递了引用?

嗯,这就是C#的工作原理。 语句中的lambda表达式构造一个词法闭包,它存储对i的单个引用,即使在循环结束后仍然存在。

要解决它,你可以做你做过的事情。

欢迎在Web上阅读有关此特定问题的更多信息; 我的选择将是Eric Lippert在这里的讨论。

如果从范围的角度来看待发生的情况,这将更容易理解:

 for (int i = 0; i < 10; i++) { Thread t = new Thread(() => new PhoneJobTest(i); t.Start(); } 

基本上翻译成非常接近的东西:

 int i = 0; while (i < 10) { Thread t = new Thread(() => new PhoneJobTest(i); t.Start(); i++; } 

当你使用lambda表达式,并且它使用在lambda之外声明的变量(在你的情况下, i )时,编译器会创建一个称为闭包的东西 – 一个临时类,它“包装”i变量并将其提供给委托由lambda生成。

闭包构造在与变量(i)相同的级别,因此在您的情况下:

 int i = 0; ClosureClass = new ClosureClass(ref i); // Defined here! (of course, not called this) while (i < 10) { Thread t = new Thread(() => new PhoneJobTest(i); t.Start(); i++; } 

因此,每个Thread都定义了相同的闭包

当您重新设计循环以使用临时时,将在该级别生成闭包:

 for (int i = 0; i < 10; i++) { int jobNum = i; ClosureClass = new ClosureClass(ref jobNum); // Defined here! Thread t = new Thread(() => new PhoneJobTest(jobNum); t.Start(); } 

现在,每个Thread都有自己的实例,一切正常。

简短回答:关闭。 这里给出的答案很长(在其他地方): 启动线程时的不同行为:ParameterizedThreadStart与Anonymous Delegate。 为什么这有关系?

你肯定想读Eric Lippert的“关闭循环变量被认为有害”:

  • 第1部分
  • 第2部分

简而言之:您看到的行为正是C#的工作原理。

这是因为C#将参数传递给lambda的方式。 它将变量访问包装在编译期间创建的类中,并将其作为字段公开给lambda主体。

使用匿名委托或lambda表达式时,会创建一个闭包 ,以便引用外部变量。 创建闭包时,堆栈(值)变量将提升到堆。

避免这种情况的一种方法是使用ParameterizedThreadStart委托启动线程。 例如:

  static void Main() { for (int i = 0; i < 10; i++) { bool flag = false; var parameterizedThread = new Thread(ParameterizedDisplayIt); parameterizedThread.Start(flag); flag = true; } Console.ReadKey(); } private static void ParameterizedDisplayIt(object flag) { Console.WriteLine("Param:{0}", flag); } 

巧合的是,我昨天遇到了这个概念: Link