2009-11-02 71 views
7

在以下程序中,DummyMethod始終會打印5.但是,如果我們使用註釋代碼,則會得到不同的值(即1,2,3,4)。任何人都可以解釋爲什麼這會發生嗎?C#中的代表問題

 delegate int Methodx(object obj); 

     static int DummyMethod(int i) 
     { 
      Console.WriteLine("In DummyMethod method i = " + i); 
      return i + 10; 
     } 

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

      for (int i = 0; i < 5; ++i) 
      { 
       methods.Add(delegate(object obj) { return DummyMethod(i); }); 
      } 

      //methods.Add(delegate(object obj) { return DummyMethod(1); }); 
      //methods.Add(delegate(object obj) { return DummyMethod(2); }); 
      //methods.Add(delegate(object obj) { return DummyMethod(3); }); 
      //methods.Add(delegate(object obj) { return DummyMethod(4); }); 

      foreach (var method in methods) 
      { 
       int c = method(null); 
       Console.WriteLine("In main method c = " + c); 
      } 
     } 

此外,如果使用下面的代碼,我會得到所需的結果。

 for (int i = 0; i < 5; ++i) 
     { 
      int j = i; 
      methods.Add(delegate(object obj) { return DummyMethod(j); }); 
     } 
+2

收取這個問題。這種行爲只是感覺「危險」。我敢打賭,隨着C#越來越多地轉向面向代表的封裝行爲方式,它將會更頻繁地出現,因爲它們是難以調試的問題之一。這就像開關條款的循環等價物被允許在沒有強制性的「休息」的情況下向下級聯一樣。條款 – 2009-11-02 10:57:46

+0

@尼爾:我同意這是一個問題熱點。當用「foreach」看到它時會更加困惑 - 以至於C#團隊正在考慮改變這種情況下的行爲。 (很難想象foreach行爲是否合意的情況; for循環的情況更容易理解,因爲很明顯變量只聲明一次。) – 2009-11-02 11:18:07

+0

[C#Captured Variable In Loop](http: //www.stackoverflow.com/questions/271440/c-sharp-captured-variable-in-loop) – nawfal 2013-11-02 06:09:22

回答

17

的問題是,你正在拍攝同一變量i在每一個代表 - 它通過循環剛剛結束的值爲5

而是希望每位代表以捕捉不同可變的,這意味着在循環聲明一個新的變量:

for (int i = 0; i < 5; ++i) 
{ 
    int localCopy = i; 
    methods.Add(delegate(object obj) { return DummyMethod(localCopy); }); 
} 

這是一個很常見的「疑難雜症」 - 你可以閱讀更多的關於my closures article捕捉變量和封鎖。

+1

喬恩,試圖讓我的頭爲什麼會發生這種情況。我把「我」看作是一種價值型思維,「這些都是作爲副本來傳遞的」,所以我看不到它看起來有什麼參考性行爲......你有一個總結/一行指着我正確的方向? – 2009-11-02 10:48:05

+2

看看最後的鏈接。讓你頭腦清醒的是,它不是被捕獲的變量的*值*,它是變量本身。 – 2009-11-02 10:50:13

+1

想一想:就好像有一點內聯事件在方法中的那個位置被調用,因此可以訪問所有變量和狀態,只有當方法的調用沒有創建時纔會獲得變量和狀態。因此,當您使用匿名方法調用方法時,您只需訪問我。 – RCIX 2009-11-02 10:57:40

2

我認爲這是因爲變量i被投入堆(這是捕獲變量

看看this answer

+1

我認爲「捕獲的變量」是更有用的術語 - 正是因爲它是*變量*被捕獲而不是它的*值*。 – 2009-11-02 10:50:48

+0

@Jon:聽起來很合理,我解決了 – 2009-11-02 11:02:47

4

如果你看看(使用反射),你可以看到其中的差別生成的代碼:

private static void Method2() 
{ 
    List<Methodx> list = new List<Methodx>(); 
    Methodx item = null; 
    <>c__DisplayClassa classa = new <>c__DisplayClassa(); 
    classa.i = 0; 
    while (classa.i < 5) 
    { 
     if (item == null) 
     { 
      item = new Methodx(classa.<Method2>b__8); 
     } 
     list.Add(item); 
     classa.i++; 
    } 
    foreach (Methodx methodx2 in list) 
    { 
     Console.WriteLine("In main method c = " + methodx2(null)); 
    } 
} 

當你使用它在後臺創建臨時類的初始化代碼,這個類持有的一個參考「我」是變量,所以根據Jon的回答,你只能看到這個的最終值。

private sealed class <>c__DisplayClassa 
{ 
    // Fields 
    public int i; 

    // Methods 
    public <>c__DisplayClassa(); 
    public int <Method2>b__8(object obj); 
} 

我真的建議在Reflector看代碼,看看這是怎麼回事,它的我是如何做到捕捉變量的意義。確保在選項菜單中將代碼優化設置爲「.NET 1.0」,否則會隱藏所有幕後的內容。