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

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

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

Это то, что выглядит в моем редакторе:

abovementioned error message in a hover popup

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

Это о версии "CLR"? Связано ли это с "ИЛ"?

Ответ 1

В этом предупреждении есть две части. Первое - это...

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

... который не является недействительным per se, но на первый взгляд он интуитивно интуитивно понятен. Это также очень трудно сделать правильно. (Настолько, что статья, ссылка на которую ниже, описывает это как "вредную".)

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

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

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 пять раз на консоль.

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

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

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

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

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

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

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

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

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

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

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

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

Есть ли релевантность с IL?

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

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

Ответ 2

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

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

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

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

to

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

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

Ответ 3

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

foreach (var entry in entries)
{
   var en = entry; 
   var result = DoSomeAction(o => o.Action(en));
}