2013-05-28 3 views
11

Следующий код выводит 33 вместо 012. Я не понимаю, почему новая переменная loopScopedi не фиксируется на каждой итерации, а не захватывает одну и ту же переменную.C# Объявление переменных внутри лямбда-выражений

Action[] actions = new Action[3]; 

for (int i = 0; i < 3; i++) 
{ 

    actions [i] =() => {int loopScopedi = i; Console.Write (loopScopedi);}; 
} 

foreach (Action a in actions) a();  // 333 

Скорее всего, этот код производит 012. В чем разница между этими двумя?

Action[] actions = new Action[3]; 

for (int i = 0; i < 3; i++) 
{ 
    int loopScopedi = i; 
    actions [i] =() => Console.Write (loopScopedi); 
} 

foreach (Action a in actions) a();  // 012 
+1

А это модифицированная проблема закрытия - см. Http://stackoverflow.com/questions/235455/access-to-modified-closure –

+1

Проблема закрытия: http://www.codethinked.com/c-closures-explained например. Поведение отличается от .net < 4.5 and > = 4.5 –

+2

Прочтите эти 2 записи от Эрика Липперта http://blogs.msdn.com/b/ericlippert/archive/2009/11/16/closing-over-the-loop-variable -part -two.aspx и http://blogs.msdn.com/b/ericlippert/archive/2009/11/12/closing-over-the-loop-variable-considered-harmful.aspx – Icarus

ответ

7

Это называется «доступ к модифицированному закрытия». В принципе, существует только одна переменная i, и все три лямбда ссылаются на нее. В конце одна переменная i была увеличена до 3, поэтому все три действия печатают 3. (Обратите внимание, что int loopScopedi = i в лямбды работает только после вызова лямбды позже.)

Во второй версии, вы создаете новую int loopScopedi для каждой итерации, и установить его к текущему значению i, который 0 и 1 и 2, для каждой итерации.

Вы можете попробовать себе встраивание в лямбды, чтобы более ясно увидеть, что происходит:

foreach (Action a in actions) 
{ 
    int loopScopedi = i; // i == 3, since this is after the for loop 
    Console.Write(loopScopedi); // always outputs 3 
} 

Versus:

foreach (Action a in actions) 
{ 
    // normally you could not refer to loopScopedi here, but the lambda lets you 
    // you have "captured" a reference to the loopScopedi variables in the lambda 
    // there are three loopScopedis that each saved a different value of i 
    // at the time that it was allocated 
    Console.Write(loopScopedi); // outputs 0, 1, 2 
} 
2

В чем разница между ними?

Различные области применения.

В вашем первом цикле вы ссылаетесь на переменную i, которая определена в области операторов цикла for, а во втором цикле вы используете локальную переменную. Выход 333 происходит из-за того, что ваш первый цикл повторяется 3 раза, и соответственно переменная i увеличивается в 3 раза, а затем, когда вы вызываете действия, все они относятся к той же переменной (i).

Во втором цикле вы используете новую переменную для каждогоAction так что вы получите 012.

2

Переменные, захваченные в лямбда поднимаются в класс разделяемой между лямбда и внешний код.

В вашем первом примере i поднимается один раз и используется как с for(), так и со всеми переданными лямбдами. К тому времени, когда вы достигнете Console.WriteLine, i достиг 3 от петли for().

В вашем втором примере для каждого прогона цикла поднимается новый loopScopedi, поэтому он не подвергается воздействию последующих циклов.

2

Речь идет о том, как C# закрывает крышки. В первом примере закрытие не будет выполнено правильно, и вы в конечном итоге будете использовать последнее значение всегда; но во втором примере вы фиксируете текущее значение переменной цикла в заполнителе, а затем используете этот заполнитель; который обеспечивает правильное решение.

И есть различия между тем, как C# захватывает переменную цикла в циклах foreach и для циклов в C# 5.0 и предыдущих версиях - это нарушение.

У меня был (почти) тот же вопрос, и я узнал об этом here.

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