Hay dos partes en esta advertencia. El primero es...
Acceso a cada variable en cierre
... que no es inválido per se pero es contraintuitivo a primera vista. También es muy difícil hacerlo bien. (Tanto es así que el artículo que enlazo a continuación describe esto como "dañino").
Tome su consulta y observe que el código que ha extraído es básicamente una forma expandida de lo que genera el compilador de C # (antes de C # 5) para foreach1 :
[No] entiendo por qué [lo siguiente] no es válido:
string s; while (enumerator.MoveNext()) { s = enumerator.Current; ...
Bueno, es válido sintácticamente. Y si todo lo que está haciendo en su ciclo es usar el valor de, sentonces todo está bien. Pero cerrarse sconducirá a un comportamiento contrario a la intuición. Eche un vistazo al siguiente código:
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);
}
}
Si ejecuta este código, obtendrá el siguiente resultado de consola:
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
Esto es lo que esperabas.
Para ver algo que probablemente no espere, ejecute el siguiente código inmediatamente después del código anterior:
foreach (var action in countingActions)
action();
Obtendrá la siguiente salida de consola:
s == 5
s == 5
s == 5
s == 5
s == 5
¿Por qué? Porque creamos cinco funciones que hacen exactamente lo mismo: imprimir el valor de s(que hemos cerrado). En realidad, son la misma función ("Imprimir s", "Imprimir s", "Imprimir s" ...).
En el punto en el que vamos a utilizarlos, hacen exactamente lo que les pedimos: imprimir el valor de s. Si observa el último valor conocido de s, verá que es 5. Entonces nos s == 5imprimen cinco veces en la consola.
Que es exactamente lo que pedimos, pero probablemente no lo que queremos.
La segunda parte de la advertencia ...
Puede tener un comportamiento diferente cuando se compila con diferentes versiones del compilador.
...Es lo que es. A partir de C # 5, el compilador genera un código diferente que "evita" que esto suceda a través deforeach .
Por lo tanto, el siguiente código producirá resultados diferentes en diferentes versiones del compilador:
foreach (var n in numbers)
{
Action action = () => Console.WriteLine("n == {0}", n);
countingActions.Add(action);
}
En consecuencia, también producirá la advertencia R # :)
Mi primer fragmento de código, arriba, exhibirá el mismo comportamiento en todas las versiones del compilador, ya que no lo estoy usando foreach(más bien, lo he expandido como lo hacen los compiladores anteriores a C # 5).
¿Es esto para la versión CLR?
No estoy muy seguro de lo que estás preguntando aquí.
La publicación de Eric Lippert dice que el cambio ocurre "en C # 5". Entoncespresumiblemente tienes que apuntar a .NET 4.5 o posterior con un compilador de C # 5 o posterior para obtener el nuevo comportamiento, y todo lo anterior obtiene el comportamiento anterior.
Pero para ser claros, es una función del compilador y no la versión de .NET Framework.
¿Existe relevancia con IL?
Un código diferente produce un IL diferente, por lo que en ese sentido hay consecuencias para el IL generado.
1 foreach es una construcción mucho más común que el código que ha publicado en su comentario. El problema suele surgir mediante el uso de foreach, no mediante la enumeración manual. Es por eso que los cambios foreachen C # 5 ayudan a prevenir este problema, pero no por completo.