2014-03-25 77 views
2

我这一段代码,局部变量范围违规,C#?

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”的十倍。

我的范围是在for循环中。但是,尽管我们在外面检索到我们仍然从我身上获得价值。

这怎么可能?

+0

[关闭循环变量被认为是有害的](http://blogs.msdn.com/b/ericlippert/archive/2009/11/12/closing-over-the-loop-variable-considered-harmful。 aspx)在'foreach'循环的上下文中讨论了这一点,但同样的推理也代表'for'循环。另外,你会注意到'foreach'循环现在已经改变了。 –

回答

0

每个代表仅在foreach之后,在for循环之后调用。而此时,可变i捕获由封闭已经处于其最终值,即10.可以解决像这样:

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

但是,I /缓存的范围呢?它是本地的循环..对吗? – Dhana

+0

这不是一个范围问题。这是一个执行时间问题。看看@Sweko解开了什么。 – StuartLC

2

您修改关闭。试试这个:

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

当你用你的匿名方法访问你本地作用域中的变量时,它会创建闭包。

+0

但是I/II的范围呢?它是本地的循环..对吗? – Dhana

+0

是的但没有修改,在每个循环中我们创建新的本地'ii'。 –

2

委托中的代码不会运行,直到它被调用,这发生在第二个循环中。然后,它指的是这是第一循环的范围内定义的i,但与它的当前值 - 由于第一循环已经完成,i将是10各一次。

我相信你创建的每个委托都被赋予与第一个循环相同的范围,如果这样做有道理的话。这意味着每个i都有它委托作为它的范围,因为每个代表在第一循环的范围内定义的,每个i也会有环路,因为它的范围,即使代表逻辑被称为该范围,如你的例子。

由于i有效整个/横跨多个循环迭代,它就会被更新,并始终由10代表被调用的时间。

这就解释了为什么以下工作作为修复:

for(int i = 0; i < 10; i++) 
{ 
    var localVar = i; // Only valid within a single iteration of the loop! 
    printers.Add(delegate { Console.WriteLine(localVar); }); 
} 
+0

但是我的范围呢?它是本地的循环..对吗? – Dhana

+0

'i'被捕获,所以它的范围有所扩展。从[这里](http://msdn.microsoft.com/en-us/library/bb397687.aspx) - “捕获的变量不会被垃圾收集,直到引用它的委托才有资格进行垃圾回收。 “ – SWeko

+0

@Dhana我已经更新了我的答案,以提供更多信息。 – Kjartan

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 /委托中使用的变量不会被垃圾收集,直到委托本身超出作用域 - 这可能是大对象的问题。具体来说,7.15.5节。的C#5规格的1规定:

当外变量由匿名函数引用,则 外变量据说已经由匿名 功能捕获。通常,局部变量的生命周期限于 执行与其关联的块或语句 (第5.1.7节)。但是,捕获的外部变量的生存期至少延长至 ,直到从 创建的委托或表达式树变为符合垃圾回收条件。