2009-07-14 68 views
2

我知道,有匿名函數,局部堆棧變量都提升到一個類饒人,現在都在堆等,所以下面不工作:匿名函數的局部變量吊裝的方式

using System; 
using System.Collections.Generic; 
using System.Linq; 

namespace AnonymousFuncTest 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      foreach (var f in GetFuncs()) 
      { 
       Console.WriteLine(f()); 
      } 
      Console.ReadLine(); 
     } 

     static IEnumerable<Func<int>> GetFuncs() 
     { 
      List<Func<int>> list = new List<Func<int>>(); 
      foreach(var i in Enumerable.Range(1, 20)) 
      { 
       list.Add(delegate() { return i; }); 
      } 

      return list; 
     } 
    } 
} 

我知道改變GetFuncs到這會工作:

static IEnumerable<Func<int>> GetFuncs() 
    { 
     foreach(var i in Enumerable.Range(1, 20)) 
     { 
      yield return() => i; 
     } 
    } 

不得不說我在做類似如下:

  foreach (var arg in someArgList) 
      { 
       var item = new ToolStripMenuItem(arg.ToString()); 
       ritem.Click += delegate(object sender, EventArgs e) 
       { 
        new Form(arg).Show(); 
       }; 
       mainMenu.DropDownItems.Add(ritem); 
      }     

這當然沒有預期的效果。我知道它爲什麼不起作用,只需要建議如何解決它就可以了。

+0

這個問題,關閉捕捉的foreach的單迭代變量,而不是通過循環每次拍攝不同的變量,是最常見的頭號「這個代碼不能像我期望的那樣」出現我們得到的錯誤報告。我們正在考慮在未來版本的語言中採取突破性變化,並將迭代變量在邏輯上移入循環中。如果有人知道現實世界的代碼會因爲這種變化而中斷,請發郵件給我。我的博客上有一個「聯繫我」鏈接。謝謝! – 2009-07-14 21:56:22

回答

9

你應該改變這樣的:

static IEnumerable<Func<int>> GetFuncs() 
    { 
     List<Func<int>> list = new List<Func<int>>(); 
     foreach (var i in Enumerable.Range(1, 20)) 
     { 
      int i_local = i; 
      list.Add(() => i_local); 
     } 

     return list; 
    } 

編輯

感謝喬恩斯基特,讀他的答案。

9

只是爲了詳細闡述kek444的答案,問題不在於本地變量被捕獲 - 而是所有代表都在捕獲局部變量。

在循環中使用變量的副本時,每次迭代循環都會有一個新變量「實例化」,因此每個委託都捕獲一個不同的變量。有關更多詳細信息,請參閱my article on closures


另一種方法:

對於這種特殊的情況下,實際上是使用LINQ一個不錯的選擇:

static IEnumerable<Func<int>> GetFuncs() 
{ 
    return Enumerable.Range(1, 20) 
        .Select(x => (Func<int>)(() => x)) 
        .ToList(); 
} 

如果你想偷懶的評價,你能不能別再ToList()通話。

+0

感謝您的精心製作!我想我只是在等待看看評論中是否出現了問題。 :) – 2009-07-14 20:07:38

+0

謝謝Skeet先生,一如既往的信息。因爲最終我需要匿名函數作爲事件處理函數,我將使用局部變量路由,不能使用LINQ替代方法。 – 2009-07-14 20:15:16

+0

@Jon:爲什麼明確的Func 在選擇?不應該推斷這一點嗎? – Janie 2009-07-14 20:53:37

1

您可以通過將循環變量複製到循環局部變量來正確捕獲循環變量的值。

static IEnumerable<Func<Int32>> GetFuncs() 
{ 
    List<Func<Int32>> list = new List<Func<Int32>>(); 

    foreach(Int32 i in Enumerable.Range(1, 20)) 
    { 
     Int32 local_i = i; 
     list.Add(delegate() { return local_i; }); 
    } 

    return list; 
} 
0

來表達你的最後一個例子一個替代方法:

foreach (var item in someArgList 
       .Select(a => 
         var i = new ToolStripMenuItem(a.ToString()); 
         i.Click+= (sender, e) => new Form(a).Show(); 
         return i;) 
     ) 
{ 
    mainMenu.DropDownItems.Add(item); 
} 

在foreach循環不好的封閉/捕獲的修復通常是.Select()通話。

0

這工作:

List<Func<int>> list = new List<Func<int>>();   
Enumerable.Range(1, 20).ToList().ForEach(i => { 
    list.Add(delegate() { return i; });    
}); 

那麼,這是否:

Action action; 
List<Action> objects = new List<Action>(); 
var items = new string [] { "whatever", "something" }; 
items.ToList().ForEach((arg) => { 
    action =() => Console.WriteLine(arg.ToString()); 
    objects.Add(action); 
}); 
objects[0](); // prints whatever 
objects[1](); // prints something