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