Эта проблема сообщается несколько раз в неделю в StackOverflow.Проблема заключается в том, что каждая новая лямбда, созданная внутри цикла, имеет общую переменную , аналогичную .Лямбды не фиксируют значение, они захватывают переменную.То есть, когда вы говорите
List<Action> list = new List<Action>();
foreach(int x in Range(0, 10))
list.Add( ()=>{Console.WriteLine(x);} );
list[0]();
, это, конечно, печатает «10», потому что это значение x сейчас .Действие - «записать текущее значение x», а не «записать значение, которое x вернуло при создании делегата».
Чтобы обойти эту проблему, создайте новую переменную:
List<Action> list = new List<Action>();
foreach(int x in Range(0, 10))
{
int y = x;
list.Add( ()=>{Console.WriteLine(y);} );
}
list[0]();
Поскольку эта проблема настолько распространена, мы рассматриваем возможность изменения следующей версии C #, чтобы новая переменная создавалась каждый раз в цикле foreach.
См. http://ericlippert.com/2009/11/12/closing-over-the-loop-variable-considered-harmful-part-one/ для получения дополнительной информации.
ОБНОВЛЕНИЕ: Из комментариев:
Каждая ICommand имеет один и тот же метод информации:
{ Method = {Void <CreateCommands>b__0(System.Object)}}
Да, конечно, это так.Метод один и тот же каждый раз.Я думаю, вы неправильно понимаете, что такое создание делегата.Рассмотрим этот вариант.Предположим, вы сказали:
var firstList = new List<Func<int>>()
{
()=>10, ()=>20
};
ОК, у нас есть список функций, которые возвращают целые числа.Первый возвращает 10, второй возвращает 20.
Это то же самое, что и:
static int ReturnTen() { return 10; }
static int ReturnTwenty() { return 20; }
...
var firstList = new List<Func<int>>()
{ ReturnTen, ReturnTwenty };
Имеет смысл пока?Теперь мы добавим ваш цикл foreach:
var secondList = new List<Func<int>>();
foreach(var func in firstList)
secondList.Add(()=>func());
ОК, что означает , что означает?Это означает то же самое, что и:
class Closure
{
public Func<int> func;
public int DoTheThing() { return this.func(); }
}
...
var secondList = new List<Func<int>>();
Closure closure = new Closure();
foreach(var func in firstList)
{
closure.func = func;
secondList.Add(closure.DoTheThing);
}
Теперь понятно, что здесь происходит?Каждый раз в цикле вы не создаете новое замыкание и, конечно, не создаете новый метод.Создаваемый вами делегат всегда указывает на один и тот же метод и всегда на одно и то же замыкание.
Теперь, если вместо этого вы написали
foreach(var loopFunc in firstList)
{
var func = loopFunc;
secondList.Add(func);
}
, то сгенерированный нами код будет
foreach(var loopFunc in firstList)
{
var closure = new Closure();
closure.func = loopFunc;
secondList.Add(closure.DoTheThing);
}
Теперь у каждой новой функции в списке есть такой же метод информации - это все еще DoTheThing - но другое закрытие .
Теперь имеетимеет смысл, почему вы видите свой результат?
Возможно, вы захотите также прочитать:
Каково время жизни делегата, созданного лямбда-выражением в C #?
ДРУГОЕ ОБНОВЛЕНИЕ: Из отредактированного вопроса:
То, что я пытался согласно предложениям, но не помогло:
foreach (var action in actionArray)
{
Action<object> executeHandler = o => { action(); };
commands.Add(new RelayCommand(executeHandler)); }
}
Конечно, это сделалнет помощи.Это та же проблема, что и раньше. Проблема в том, что лямбда закрывается по единственной переменной «действие», а не по каждому значению действия. Перемещение туда, где создается лямбда, очевидно, не решает эту проблему.Что вы хотите сделать, это создать новую переменную .Ваше второе решение делает это, выделяя новую переменную, создавая поле ссылочного типа.Вам не нужно делать это явно;как я упоминал выше, компилятор сделает это за вас, если вы создадите новую переменную внутри тела цикла.
Правильный и короткий способ решения проблемы -
foreach (var action in actionArray)
{
Action<object> copy = action;
commands.Add(new RelayCommand(x=>{copy();}));
}
Таким образомВы создаете новую переменную каждый раз в цикле, и поэтому каждая лямбда в цикле закрывается по другой переменной .
Каждый делегат будет иметь methodinfo но другое закрытие .
Я не совсем уверен насчет этих закрытий и лямбд
Вы делаете выше-Закажите функциональное программирование в вашей программе. Тебе лучше узнать об "этих замыканиях и лямбдах", если ты хочешь иметь возможность делать это правильно. Нет времени, подобного настоящему.