Я знаю, что с анонимными функциями локальные переменные стека продвигаются в класс, теперь находятся в куче и т.д. Таким образом, следующее не работает:
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);
}
Это, конечно, не имеет предполагаемого эффекта. Я знаю, почему это не работает, просто нужны предложения о том, как его исправить, чтобы он это сделал.
Вы должны изменить его следующим образом:
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;
}
ИЗМЕНИТЬ
Благодаря Джону Скиту, прочитайте его ответ.
Чтобы уточнить ответ kek444, проблема заключается не в том, что локальные переменные захватываются - это то, что одна и та же локальная переменная захватывается всеми вашими делегатами.
Используя копию переменной внутри цикла, на каждой итерации цикла создается "экземпляр" новой переменной, поэтому каждый делегат захватывает другую переменную. Подробнее см. мою статью о закрытии.
Альтернативный подход:
В этой конкретной ситуации на самом деле есть хорошая альтернатива, использующая LINQ:
static IEnumerable<Func<int>> GetFuncs()
{
return Enumerable.Range(1, 20)
.Select(x => (Func<int>)(() => 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;
}
Альтернативный способ выразить свой последний пример:
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; });
});
Так делает это:
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