Recibo la siguiente advertencia:
Acceso a cada variable en cierre. Puede tener un comportamiento diferente cuando se compila con diferentes versiones del compilador.
Así es como se ve en mi editor:
Sé cómo solucionar esta advertencia, pero quiero saber por qué recibiría esta advertencia.
¿Se trata de la versión "CLR"? ¿Está relacionado con "IL"?
Respuestas:
Hay dos partes en esta advertencia. El primero es...
... 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
foreach
1 :Bueno, es válido sintácticamente. Y si todo lo que está haciendo en su ciclo es usar el valor de,
s
entonces todo está bien. Pero cerrarses
conducirá 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 ("Imprimirs
", "Imprimirs
", "Imprimirs
" ...).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 des
, verá que es5
. Entonces noss == 5
imprimen cinco veces en la consola.Que es exactamente lo que pedimos, pero probablemente no lo que queremos.
La segunda parte de la advertencia ...
...Es lo que es. A partir de C # 5, el compilador genera un código diferente que "evita" que esto suceda a través de
foreach
.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).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". Entonces
presumiblemente tienes que apuntar a .NET 4.5 o posteriorcon 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.
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 deforeach
, no mediante la enumeración manual. Es por eso que los cambiosforeach
en C # 5 ayudan a prevenir este problema, pero no por completo.fuente
foreach
material aquí proviene del contenido de la pregunta. Tienes razón en que puede suceder de varias formas más generales.La primera respuesta es genial, así que pensé en agregar una cosa.
Recibirá la advertencia porque, en su código de ejemplo, a reflectModel se le está asignando un IEnumerable, que solo se evaluará en el momento de la enumeración, y la enumeración en sí podría ocurrir fuera del ciclo si asignó reflectModel a algo con un alcance más amplio .
Si cambiaste
...Where(x => x.Name == property.Value)
a
...Where(x => x.Name == property.Value).ToList()
luego a reflectModel se le asignaría una lista definida dentro del bucle foreach, por lo que no recibiría la advertencia, ya que la enumeración definitivamente ocurriría dentro del bucle y no fuera de él.
fuente
Una variable de ámbito de bloque debería resolver la advertencia.
foreach (var entry in entries) { var en = entry; var result = DoSomeAction(o => o.Action(en)); }
fuente