Acceso a la variable foreach en advertencia de cierre

Recibo la siguiente advertencia:

Acceso a la variable foreach en el cierre. Puede tener un comportamiento diferente cuando se comstack con diferentes versiones del comstackdor.

Esto es lo que parece en mi editor:

mensaje de error mencionado anteriormente en una ventana emergente emergente

Sé cómo solucionar esta advertencia, pero quiero saber por qué recibiría esta advertencia.

¿Esto se trata de la versión “CLR”? ¿Está relacionado con “IL”?

Hay dos partes en esta advertencia. El primero es…

Acceso a la variable foreach en el cierre

… que no es inválido per se pero no es intuitivo a primera vista. También es muy difícil hacerlo bien. (Tanto que el artículo que enlazo a continuación describe esto como “dañino”).

Haga su consulta, señalando que el código que ha extraído es básicamente una forma expandida de lo que el comstackdor de C # (antes de C # 5) genera para foreach 1 :

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ás haciendo en tu ciclo usa el valor de s entonces todo está bien. Pero el cierre de s dará lugar a un comportamiento contrario a la intuición. Eche un vistazo al siguiente código:

 var countingActions = new List(); 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 la 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 esperas.

Para ver algo que probablemente no esperes, ejecuta el siguiente código inmediatamente después del código anterior:

 foreach (var action in countingActions) action(); 

Obtendrá el siguiente resultado de la consola:

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

¿Por qué? Porque creamos cinco funciones que hacen exactamente lo mismo: imprima el valor de s (que hemos cerrado). En realidad, tienen la misma función (“Imprimir s “, “Imprimir s “, “Imprimir s ” …).

En el momento en que los usamos, hacen exactamente lo que pedimos: imprimir el valor de s . Si miras el último valor conocido de s , verás que es 5 . Así que tenemos s == 5 impreso cinco veces en la consola.

Que es exactamente lo que pedimos, pero probablemente no es lo que queremos.

La segunda parte de la advertencia …

Puede tener un comportamiento diferente cuando se comstack con diferentes versiones del comstackdor.

…Es lo que es. Comenzando con C # 5, el comstackdor 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 bajo diferentes versiones del comstackdor:

 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 comstackdor, ya que no estoy utilizando foreach (mejor dicho, lo he expandido de la misma forma que los comstackdores 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”. Asi que presumiblemente debe apuntar a .NET 4.5 o posterior con un comstackdor 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 comstackdor y no de la versión de .NET Framework.

¿Hay relevancia con IL?

El código diferente produce diferentes IL, 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 generalmente surge a través del uso de foreach , no a través de una enumeración manual. Es por eso que los cambios a foreach en C # 5 ayudan a prevenir este problema, pero no completamente.

La primera respuesta es genial, así que pensé que solo agregaría una cosa.

Recibirá la advertencia porque, en su código de ejemplo, se le asigna a un modelo reflejado un IEnumerable, que solo se evaluará en el momento de la enumeración, y la enumeración misma podría ocurrir fuera del ciclo si asignara el modelo reflejado a algo con un scope más amplio .

Si cambiaste

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

a

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

a continuación, se le asignaría una lista definida a reflectEmode dentro del bucle foreach, de modo que no recibiría la advertencia, ya que la enumeración definitivamente ocurriría dentro del ciclo, y no fuera de él.

Una variable con ámbito de bloque debería resolver la advertencia.

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