Ошибка "Access to modified closure"

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

 IEnumerable<char> query = "Not what you might expect";

 foreach (char vowel in "aeiou")
    query = query.Where(c => c != vowel);

    Console.WriteLine("Error:");
    foreach (char c in query)
    Console.Write(c); // Not what yo might expect

Казалось бы, все хорошо (если, конечно, не установлен Visual Assist или Resharper, тогда мы увидим предупреждение «Access to modified closure»), однако, после выполнения программы мы получим строчку «Not what yo might expect», удалилась только последняя буква. Чтобы понять, что произошло, нужно вспомнить, во что преобразуется цикл foreach, а преобразуется он в следующее:


IEnumerable<char> vowels = "aeiou";
using (IEnumerator<char> rator = vowels.GetEnumerator())
{
  char vowel;
  while (rator.MoveNext())
  {
    vowel = rator.Current;
    query = query.Where (c => c != vowel);
  }
}

На каждом шаге цикла мы добавляем в качестве условия проверку на неравенство переменной vowel. «Ленивый» оператор where захватывает одну и ту же переменную, поэтому при обращении к последовательности IEnumerable проверка и будет проходить на неравенство только одной переменной, получившей последнее значение. Для того, чтобы получить ожидаемый результат, необходимо добавить внутри цикла локальную переменную — тогда каждый раз будет захватываться новая переменная с новым значением.


foreach (char vowel in "aeiou")
{
  char cur = vowel;
  query = query.Where(c => c != cur);
}

Комментарии:

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *