Подтвердить что ты не робот

С# lambda, значение локальной переменной не принято, когда вы думаете?

Дано:

void AFunction()
{

   foreach(AClass i in AClassCollection)
   {
      listOfLambdaFunctions.AddLast(  () =>  {  PrintLine(i.name); }  );
   }
}

void Main()
{
    AFunction();
    foreach( var i in listOfLambdaFunctions)
       i();
}

теперь вы могли бы подумать, что это сделало бы equivilant:

void Main()
{

    foreach(AClass i in AClassCollection)
       PrintLine(i.name);
}

но это не так, что он будет делать, это каждый раз печатать имя последнего элемента в AClassCollection! поэтому в каждом лямбда-функции использовался один и тот же элемент. я подозревал, что может быть какая-то задержка в "когда была создана лямбда" или "когда она взяла снимок внешних переменных, используемых в ней", или в основном, просто удерживая "ссылку на локальную переменную i"

поэтому я сделал это:

string astr = "a string";
AFunc fnc = () => { System.Diagnostics.Debug.WriteLine(astr); };
astr = "chagnged!";
fnc();

и удивление, удивление, оно выводит "изменено!"

Я использую XNA 3.1 (любой С#, который есть)

что происходит? делает ли лямбда-функцию каким-то образом "ссылкой" на переменную или что-то еще? все равно вокруг этой проблемы?

4b9b3361

Ответ 1

Это модифицированное закрытие

Смотрите: похожие вопросы, такие как Доступ к измененному закрытию

Чтобы обойти проблему, вы должны сохранить копию переменной внутри области цикла for:

   foreach(AClass i in AClassCollection) 
   { 
      AClass anotherI= i;
      listOfLambdaFunctions.AddLast(  () =>  {  PrintLine(anotherI.name); }  ); 
   } 

Ответ 2

ли лямбда-функция каким-то образом хранит "ссылку" на переменную или что-то еще?

Закрыть. Функция лямбда фиксирует эту переменную. Нет необходимости хранить ссылку на переменную, а на самом деле в .NET невозможно постоянно хранить ссылку на переменную. Вы просто фиксируете всю переменную. Вы никогда не фиксируете значение переменной.

Помните, что переменная является местом хранения. Имя "i" относится к определенному месту хранения, и в вашем случае оно всегда относится к одному и тому же месту хранения.

Есть ли вокруг этой проблемы?

Да. Создавайте новую переменную каждый раз через цикл. Затем замыкание захватывает другую переменную каждый раз.

Это одна из наиболее часто встречающихся проблем с С#. Мы рассматриваем возможность изменения семантики объявления переменной цикла, чтобы каждый раз через цикл создавалась новая переменная.

Подробнее об этой проблеме см. в моих статьях по теме:

http://ericlippert.com/2009/11/12/closing-over-the-loop-variable-considered-harmful-part-one/

Ответ 3

что происходит? делает ли лямбда-функцию каким-то образом "ссылкой" на переменную или что-то еще?

Да именно это; С# захваченные переменные относятся к переменной , а не к переменной. Обычно вы можете обойти это, введя временную переменную и привязав к ней:

string astr = "a string";
var tmp = astr;
AFunc fnc = () => { System.Diagnostics.Debug.WriteLine(tmp); };

особенно в foreach, где это печально известно.

Ответ 4

Да, лямбда хранит ссылку на переменную (концептуально говоря, во всяком случае).

Очень простое обходное решение:

 foreach(AClass i in AClassCollection)
   {
      AClass j = i;
      listOfLambdaFunctions.AddLast(  () =>  {  PrintLine(j.name); }  );
   }

В каждой итерации цикла foreach создается новый j, который захватывает лямбда. i, с другой стороны, является одной и той же переменной во всем, но обновляется с каждой итерацией (так что все lambdas в конечном итоге видят последнее значение)

И я согласен, что это немного удивительно.:)

Ответ 5

Я тоже был пойман этим, как сказал Калгари Кодер, это модифицированное закрытие. У меня действительно были проблемы с их поиском, пока я не получил решайер. Так как это одно из предупреждений, которое решает часы, я гораздо лучше идентифицирую их с кодом.