Перебирая список действий - PullRequest
7 голосов
/ 05 марта 2012

Я не могу понять, как просмотреть список Action.Когда я пытаюсь это сделать, я получаю значения, аналогичные предыдущей итерации.

Вот код (упрощенный пример):

string[] strings = { "abc", "def", "ghi" };

var actions = new List<Action>();
foreach (string str in strings)
    actions.Add(new Action(() => { Trace.WriteLine(str); }));

foreach (var action in actions)
    action();

Вывод:

ghi
ghi
ghi

Почему он всегда выбирает последний элемент в strings, когда выполняет действие?
И как мне получить желаемый результат, который будет:

abc
def
ghi

Ответы [ 3 ]

14 голосов
/ 05 марта 2012

Ваше действие является закрытием, поэтому оно получает доступ к str, а не к копии str:

foreach (string str in strings)
{
    var copy = str; // this will do the job
    actions.Add(new Action(() => { Trace.WriteLine(copy); }));
}
4 голосов
/ 05 марта 2012

Это поведение обусловлено замыканиями .

Переменная, которая присутствует в вашей лямбде, является ссылкой и , а не значением копии.Это означает, что это указывает на последнее значение, принятое str, которое в вашем случае является "ghi".Вот почему для каждого вызова он просто переходит в одну и ту же область памяти и восстанавливает, естественно, одно и то же значение.

Если вы пишете код, как в ответах, вы принудительно C#компилятор для регенерации нового значения каждый раз, поэтому новый адрес будет передан в labmda, поэтому у каждой лямбды будет своя собственная переменная.

Кстати, если я не ошибаюсь, команда C# обещает исправить это неестественное поведение в C# 5.0.Так что лучше проверить их блог на эту тему для будущих обновлений.

3 голосов
/ 05 марта 2012

Это довольно сложная ситуация. Краткий ответ - создать копию локальной переменной перед назначением ее закрытию:

string copy = str;
actions.Add(new Action(() => { Trace.WriteLine(copy); }));

Проверьте эту статью о замыканиях для получения дополнительной информации.

...