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

Jos*_*gry 2 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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

我知道将GetFuncs更改为可行的方法:

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

但是说我做的事情如下:

            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);
            }                
Run Code Online (Sandbox Code Playgroud)

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

Ken*_* K. 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;
    }
Run Code Online (Sandbox Code Playgroud)

编辑

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


Jon*_*eet 9

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

在循环中使用变量的副本,在循环的每次迭代中"实例化"新变量,因此每个委托捕获不同的变量.有关详细信息,请参阅我关于闭包的文章.


另一种方法:

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

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

如果你想进行懒惰的评估,你可以放弃ToList()通话.