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

时间:2009-12-18 19:10:44

标签: c#

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

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

这可以这样解决:

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

这里发生了什么?为什么原始示例传递引用?

6 个答案:

答案 0 :(得分:16)

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

为了解决这个问题,你可以做你做过的事情。

欢迎在网络上阅读有关此特定问题的更多信息;我的选择是Eric Lippert's discussion here.

答案 1 :(得分:16)

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

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变量的临时类up并将其提供给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都有自己的实例,一切正常。

答案 2 :(得分:3)

简短回答:关闭。这里给出的答案很长(在其他地方):Differing behavior when starting a thread: ParameterizedThreadStart vs. Anonymous Delegate. Why does it matter?

答案 3 :(得分:2)

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

简而言之:您看到的行为正是C#的运作方式。

答案 4 :(得分:1)

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

答案 5 :(得分:0)

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

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

        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