2012-03-05 5 views
6

Я не могу понять, как пройти через список 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 

ответ

13

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

foreach (string str in strings) 
{ 
    var copy = str; // this will do the job 
    actions.Add(new Action(() => { Trace.WriteLine(copy); })); 
} 
+0

Gah, вы выигрываете. Я знал, как это исправить, но я не мог вспомнить причину. Закрытие! Мне нужно закрыть! +1 :) – Joshua

+1

@ Joshua это было не так давно, когда я узнал немного глубже :) ... это может быть полезно для дальнейшего чтения http://stackoverflow.com/questions/9412672/lambda-expressions-with -multithreading-in-c-sharp –

+0

Интересно, я так и не понял. Благодарю. – demoncodemonkey

3

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

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

Check out this article on closures для получения дополнительной информации.

3

Это поведение обусловлено Closures.

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

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

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

+2

+1 хорошее описание. Возможно, стоит упомянуть, что Java делает это по-другому. Если вы используете «Runnable» (обычно) для запуска потока, переменные * копируются * в новый контекст. Именно поэтому Java заставляет вас сделать их «final». –

Смежные вопросы