【问题标题】:C# Closures, why is the loopvariable captured by reference?C#闭包,为什么循环变量被引用捕获?
【发布时间】:2010-12-28 03:48:06
【问题描述】:

在此示例中,我尝试按值传递,但改为传递引用。

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();
}

这里发生了什么?为什么原始示例通过引用?

【问题讨论】:

标签: c#


【解决方案1】:

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

要补救它,你可以做你所做的事情。

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

【讨论】:

    【解决方案2】:

    如果你从范围的角度来看会发生什么,这会更容易理解:

    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++;
    }
    

    因此,每个线程都获得了定义的相同的闭包

    当您修改循环以使用临时对象时,会在该级别生成闭包:

    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();
    }
    

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

    【讨论】:

    • 感谢 Reed 一个很好的视觉解释,闭包是在原始变量被实例化的级别上创建的
    【解决方案3】:

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

    【讨论】:

      【解决方案4】:

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

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

      【讨论】:

        【解决方案5】:

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

        【讨论】:

          【解决方案6】:

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

          避免这种情况的一种方法是使用 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

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多