匿名函数局部变量提升正在阻碍

时间:2009-07-14 20:01:22

标签: c#

我知道使用匿名函数,本地堆栈变量被提升为一个类,现在在堆上等等。所以以下内容不起作用:

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);
            }                

这当然没有预期的效果。我知道为什么它不起作用,只需要有关如何修复它的建议。

5 个答案:

答案 0 :(得分: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;
    }

修改

感谢Jon Skeet,请阅读他的回答。

答案 1 :(得分:9)

只是详细说明kek444的答案,问题不在于捕获局部变量 - 而是所有代表都在捕获相同的局部变量。

在循环中使用变量的副本,在循环的每次迭代中“实例化”新变量,因此每个委托捕获不同的变量。有关详细信息,请参阅my article on closures


另一种方法:

对于这种特殊情况,使用LINQ实际上有一个不错的选择:

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

如果您想进行延迟评估,可以放弃ToList()来电。

答案 2 :(得分: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;
}

答案 3 :(得分: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()的调用。

答案 4 :(得分: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