Доступ к переменной foreach в предупреждении о закрытии


86

Я получаю следующее предупреждение:

Доступ к переменной foreach при закрытии. Может иметь разное поведение при компиляции с разными версиями компилятора.

Вот как это выглядит в моем редакторе:

вышеупомянутое сообщение об ошибке во всплывающем окне

Я знаю, как исправить это предупреждение, но хочу знать, почему я получаю это предупреждение?

Это про версию "CLR"? Это связано с «Ил»?



1
TL; DR ответ: добавьте .ToList () или .ToArray () в конец выражения запроса, и это избавит от предупреждения
— JoelFan

Ответы:


136

Это предупреждение состоит из двух частей. Первый ...

Доступ к переменной foreach при закрытии

... что само по себе недействительно, но на первый взгляд противоречит интуиции. Также очень сложно поступать правильно. (Настолько, что в статье, на которую я ссылаюсь ниже, это описывается как «вредное».)

Возьмите свой запрос, отметив, что код, который вы извлекли, в основном является расширенной формой того, что компилятор C # (до C # 5) генерирует для foreach1 :

Я [не] понимаю, почему [следующее] неверно:

string s; while (enumerator.MoveNext()) { s = enumerator.Current; ...

Что ж, синтаксически это действительно так. И если все , что вы делаете в цикле использует значение из sто все хорошо. Но закрытие sприведет к противоречивому поведению. Взгляните на следующий код:

var countingActions = new List<Action>();

var numbers = from n in Enumerable.Range(1, 5)
              select n.ToString(CultureInfo.InvariantCulture);

using (var enumerator = numbers.GetEnumerator())
{
    string s;

    while (enumerator.MoveNext())
    {
        s = enumerator.Current;

        Console.WriteLine("Creating an action where s == {0}", s);
        Action action = () => Console.WriteLine("s == {0}", s);

        countingActions.Add(action);
    }
}

Если вы запустите этот код, вы получите следующий вывод консоли:

Creating an action where s == 1
Creating an action where s == 2
Creating an action where s == 3
Creating an action where s == 4
Creating an action where s == 5

Это то, чего вы ожидаете.

Чтобы увидеть то, чего вы, вероятно, не ожидаете, запустите следующий код сразу после приведенного выше:

foreach (var action in countingActions)
    action();

Вы получите следующий вывод консоли:

s == 5
s == 5
s == 5
s == 5
s == 5

Зачем? Потому что мы создали пять функций, которые делают одно и то же: выводят значение s(которое мы закрыли). На самом деле это одна и та же функция («Печать s», «Печать s», «Печать s» ...).

Когда мы приступаем к их использованию, они делают именно то, что мы просим: выводят значение s. Если вы посмотрите на последнее известное значение s, вы увидите, что это 5. Итак, мы s == 5пять раз выводим на консоль.

Это именно то, о чем мы просили, но, вероятно, не то, что мы хотим.

Вторая часть предупреждения ...

Может иметь разное поведение при компиляции с разными версиями компилятора.

... вот что это такое. Начиная с C # 5, компилятор генерирует другой код, который «предотвращает» это черезforeach .

Таким образом, следующий код будет давать разные результаты в разных версиях компилятора:

foreach (var n in numbers)
{
    Action action = () => Console.WriteLine("n == {0}", n);
    countingActions.Add(action);
}

Следовательно, он также выдаст предупреждение R # :)

Мой первый фрагмент кода, приведенный выше, будет демонстрировать одинаковое поведение во всех версиях компилятора, поскольку я не использую его foreach(скорее, я расширил его так, как это делают компиляторы до C # 5).

Это для версии CLR?

Я не совсем понимаю, о чем вы здесь спрашиваете.

В сообщении Эрика Липперта говорится, что изменение происходит «в C # 5». Такпредположительно, вам нужно настроить таргетинг на .NET 4.5 или новее с компилятором C # 5 или новее, чтобы получить новое поведение, и все, что было до этого, получает старое поведение.

Но для ясности, это функция компилятора, а не версии .NET Framework.

Есть ли актуальность с Ил?

Разный код порождает другой IL, поэтому в этом смысле для генерируемого IL есть последствия.

1 foreach - гораздо более распространенная конструкция, чем код, который вы разместили в своем комментарии. Проблема обычно возникает при использовании foreach, а не при ручном перечислении. Вот почему изменения foreachв C # 5 помогают предотвратить эту проблему, но не полностью.


7
Я действительно пробовал цикл foreach на разных компиляторах, получая разные результаты с использованием одной и той же цели (.Net 3.5). Я использовал VS2010 (который, в свою очередь, использует компилятор, связанный с .net 4.0, я считаю) и VS2012 (я считаю, компилятор .net 4.5). В принципе это означает, что если вы используете VS2013 и редактируете проект, ориентированный на .Net 3.5, и собираете его на сервере сборки, на котором установлена ​​немного более старая инфраструктура, вы можете увидеть разные результаты своей программы на вашем компьютере по сравнению с развернутой сборкой.
— Ykok

Хороший ответ, но не уверен, насколько актуален «foreach». Разве это не произойдет с ручным перечислением или даже с простым циклом for (int i = 0; i <collection.Size; i ++)? Кажется, проблема заключается в том, что замыкания выходят за пределы области видимости, или, точнее, проблема в том, что люди понимают, как ведут себя замыкания, когда они выходят за пределы области, в которой они были определены внутри.
— Brad

Материал foreachздесь исходит из содержания вопроса. Вы правы в том, что это может происходить разными, более общими способами.
— ta.speot.is

1
Почему R # все еще предупреждает меня, не читает ли он целевой фреймворк, который я установил на 4.5.
— Johnny_D

1
«Так что, по-видимому, вы должны использовать .NET 4.5 или новее». Это утверждение неверно. Версия .NET, на которую вы нацеливаетесь, не влияет на это, поведение также изменяется в .NET 2.0, 3.5 и 4, если вы используете C # 5 (VS 2012 или новее) для компиляции. Вот почему вы получаете это предупреждение только в .NET 4.0 или более ранней версии, если вы нацеливаетесь на 4.5, вы не получаете предупреждения, потому что вы не можете скомпилировать 4.5 на компиляторе C # 4 или более ранней версии.
— Скотт Чемберлен,

12

Первый ответ отличный, поэтому я решил добавить одну вещь.

Вы получаете предупреждение, потому что в вашем примере кода отраженнойModel назначается IEnumerable, который будет оцениваться только во время перечисления, а само перечисление может происходить вне цикла, если вы назначили отраженную модель чему-то с более широкой областью действия. .

Если вы изменили

...Where(x => x.Name == property.Value)

к

...Where(x => x.Name == property.Value).ToList()

тогда отраженной модели будет назначен определенный список в цикле foreach, поэтому вы не получите предупреждение, поскольку перечисление определенно произойдет внутри цикла, а не за его пределами.


Я прочитал много действительно длинных объяснений, которые не помогли мне решить эту проблему, а затем одно короткое, которое помогло. Благодарность!
— Чарльз Клейтон

Я прочитал принятый ответ и просто подумал: «Как это закрытие, если оно не связывает переменные?» но теперь я понимаю, что это когда произойдет оценка, спасибо!
— Джером

Да, это очевидное универсальное решение. Медленно, требует много памяти, но я думаю, что он действительно на 100% работает во всех случаях.
— Аль Кепп,

8

Переменная с блочной областью видимости должна устранить предупреждение.

foreach (var entry in entries)
{
   var en = entry; 
   var result = DoSomeAction(o => o.Action(en));
}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.