局部变量范围违约,C#?

时间:2014-03-25 07:47:30

标签: c# variables

我收到了这段代码,

delegate void Printer();

    static void Main(string[] args)
    {
        List<Printer> printers = new List<Printer>();

        for (int i = 0; i < 10; i++)
        {
            printers.Add(delegate { Console.WriteLine(i); });
        }

        foreach (Printer printer in printers)
        {
            printer();
        }

        Console.ReadLine();
    }

此处输出为“10”十次。

i的范围在for循环中。但是,虽然我们在外面找回我们仍然从i获得价值。

怎么可能?

4 个答案:

答案 0 :(得分:2)

你修改了封闭。试试这个:

    for (int i = 0; i < 10; i++)
    {
        int ii = i;
        printers.Add(delegate { Console.WriteLine(ii); });
    }

当您在匿名方法中使用时,访问本地范围中的变量,它会创建闭包。

答案 1 :(得分:2)

代理中的代码在调用之前不会运行,这在第二个循环中发生。然后它引用i,它是在第一个循环的范围内定义的,但是它的当前值 - 并且由于第一个循环已经完成,{{ 1}}每次都是10。

我相信你创建的每个代理都被赋予与第一个循环相同的范围,如果这是有道理的。这意味着每个i都有它的委托作为其范围,并且由于每个委托都是在第一个循环的范围内定义的,因此每个i也将循环为它的范围,即使委托逻辑被称为该范围,如在您的示例中。

由于i在循环的多次迭代中有效,因此它会更新,并且在调用委托时始终为10。

这解释了为什么以下内容可以作为修复:

i

答案 2 :(得分:0)

每个委托仅在foreach循环后的for中调用。到目前为止,闭包捕获的变量i已经处于最终值,即10。您可以这样解决:

for (int i = 0; i < 10; i++)
{
    var cache = i;
    printers.Add(delegate { Console.WriteLine(cache); });
}

答案 3 :(得分:0)

让我们展开循环:

int i=0;
printers.Add(delegate { Console.WriteLine(i); })
i=1;
printers.Add(delegate { Console.WriteLine(i); })
...
i=10;
printers.Add(delegate { Console.WriteLine(i); })

正如您所看到的,i变量是在委托中捕获的,并且委托本身在循环结束之前不会运行,并且变量已达到最后一个值(10)。

一个简单的解决方法是将循环变量分配给本地辅助变量

for (int i = 0; i < 10; i++)
{
   var index = i;
   printers.Add(delegate { Console.WriteLine(index); });
}

对于范围问题,任何捕获的变量都会扩展其范围(和生命周期)。在委托本身超出范围之前,lambda / delegate中使用的变量不会被垃圾收集 - 这对于大型对象可能是个问题。具体而言,C#5规范的第7.15.5.1节说明:

  

当外部变量被匿名函数引用时,   据说外部变量已被匿名者捕获   功能。通常,局部变量的生命周期限于   执行与之关联的块或语句   (§5.1.7)。但是,捕获的外部变量的生命周期是   至少扩展到创建的委托或表达式树   匿名函数有资格进行垃圾回收。