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

10 c#

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

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

这样可以解决:

 for (int i = 0; i < 10; i++)
{
    int jobNum = i;
    Thread t = new Thread(() => new PhoneJobTest(jobNum);
    t.Start();
}
Run Code Online (Sandbox Code Playgroud)

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

mqp*_*mqp 17

好吧,这就是C#的工作方式.语句中的lambda表达式构造一个词法闭包,i即使在循环结束后,也会存储对该表达式的单个引用.

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

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


Ree*_*sey 16

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

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

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

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

当你使用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++;
}
Run Code Online (Sandbox Code Playgroud)

因此,每个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();
}
Run Code Online (Sandbox Code Playgroud)

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

  • 感谢Reed一个很好的视觉解释,即在原始变量被实例化的级别上创建了闭包 (2认同)