2014-03-25 110 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節)。但是,捕獲的外部變量的生存期至少延長至 ,直到從 創建的委託或表達式樹變爲符合垃圾回收條件。