Ответ 1
Эта проблема сообщается несколько раз в неделю в 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]();
Так как эта проблема так распространена, мы рассматриваем возможность изменения следующей версии С#, чтобы каждая переменная создавалась каждый раз через цикл foreach.
Подробнее см. http://ericlippert.com/2009/11/12/closing-over-the-loop-variable-considered-harmful-part-one/.
ОБНОВЛЕНИЕ: Из комментариев:
Каждая ICommand имеет тот же методinfo:
{ Method = {Void <CreateCommands>b__0(System.Object)}}
Да, конечно. Метод один и тот же раз. Я думаю, вы не понимаете, что такое создание делегата. Посмотрите на это с другой стороны. Предположим, вы сказали:
var firstList = new List<Func<int>>()
{
()=>10, ()=>20
};
ОК, у нас есть список функций, возвращающих int. Первый возвращает 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);
}
Теперь каждая новая функция в списке имеет тот же методinfo - это все еще DoTheThing - но другое закрытие.
Теперь понятно, почему вы видите результат?
Вы также можете прочитать:
Каково время жизни делегата, созданного лямбдой в С#?
ДРУГОЕ ОБНОВЛЕНИЕ: Из отредактированного вопроса:
То, что я пробовал в соответствии с предложениями, но не помогло:
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();}));
}
Таким образом, вы каждый раз создаете новую переменную через цикл, и каждая лямбда в цикле поэтому закрывается по другой переменной.
Каждый делегат будет иметь тот же методinfo, но другое закрытие.
Я не уверен в этом закрытии и лямбдах
В вашей программе вы выполняете функциональное программирование более высокого порядка. Вам лучше узнать о "этих закрытиях и лямбдах", если вы хотите иметь все шансы сделать это правильно. Нет времени, как настоящее.