我收到了这段代码,
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获得价值。
怎么可能?
答案 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)。但是,捕获的外部变量的生命周期是 至少扩展到创建的委托或表达式树 匿名函数有资格进行垃圾回收。