C# 匿名函数局部变量提升正在成为阻碍

C# 匿名函数局部变量提升正在成为阻碍,c#,C#,我知道,对于匿名函数,局部堆栈变量被提升为类,现在在堆上,等等。因此以下操作不起作用: using System; using System.Collections.Generic; using System.Linq; namespace AnonymousFuncTest { class Program { static void Main(string[] args) { foreach (var f in GetF

我知道,对于匿名函数,局部堆栈变量被提升为类,现在在堆上,等等。因此以下操作不起作用:

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

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

您应该这样更改它:

    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;
    }
static IEnumerable GetFuncs()
{
列表=新列表();
foreach(可枚举范围(1,20))中的var i
{
int i_local=i;
列表.添加(()=>i_local);
}
退货清单;
}
编辑


多亏了Jon Skeet,请阅读他的答案。

请详细说明kek444的答案,问题不在于捕获了局部变量,而在于所有学员都捕获了相同的局部变量

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


另一种办法:

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

static IEnumerable<Func<int>> GetFuncs()
{
    return Enumerable.Range(1, 20)
                     .Select(x => (Func<int>)(() => x))
                     .ToList();
}
static IEnumerable GetFuncs()
{
返回可枚举范围(1,20)
.选择(x=>(Func)(()=>x))
.ToList();
}

如果需要延迟求值,只需删除
ToList()
调用。

通过将循环变量复制到循环局部变量,可以正确捕获循环变量的值

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;
}
static IEnumerable GetFuncs()
{
列表=新列表();
foreach(可枚举范围(1,20)中的Int32 i)
{
Int32本地_i=i;
Add(delegate(){return local_i;});
}
退货清单;
}

表达上一个示例的另一种方式:

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()

这可以:

List<Func<int>> list = new List<Func<int>>();        
Enumerable.Range(1, 20).ToList().ForEach(i => {
    list.Add(delegate() { return i; });            
});
List List=新列表();
Enumerable.Range(1,20).ToList().ForEach(i=>{
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
动作;
列表对象=新列表();
var items=新字符串[]{“whatever”,“something”};
items.ToList().ForEach((arg)=>{
action=()=>Console.WriteLine(arg.ToString());
对象。添加(操作);
});
对象[0]();//打印任何东西
对象[1]();//打印一些东西

感谢您的精心设计!我想我还是等着看评论中是否出现了问题。:)谢谢你,斯基特先生,一如既往,信息丰富。因为最终我需要匿名函数作为事件处理程序,我将使用局部变量路由,所以不能使用LINQ替代方法。@Jon:为什么要在Select中的lambda上强制转换显式Func?这难道不应该被推断出来吗?@Janie:不,因为Select可以投射到任何东西上。Select返回的类型是在不考虑结果如何使用的情况下确定的。这个问题,即闭包捕获foreach的单个迭代变量,而不是每次通过循环捕获不同的变量,是我们得到的第一个最常见的错误报告“此代码不像我期望的那样工作”。我们正在考虑在该语言的未来版本中进行突破性的更改,并将迭代变量移动到循环的逻辑内部。如果有人知道真实世界的代码会因为这样的更改而中断,请发电子邮件给我。我的博客上有一个“联系我”的链接。谢谢
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