Cuando se utilizan expresiones lambda o métodos anónimos en C #, debemos ser cautelosos con respecto al acceso a errores de cierre modificados . Por ejemplo:
foreach (var s in strings)
{
query = query.Where(i => i.Prop == s); // access to modified closure
...
}
Debido al cierre modificado, el código anterior hará que todas las Where
cláusulas de la consulta se basen en el valor final de s
.
Como se explica aquí , esto sucede porque la s
variable declarada en el foreach
bucle anterior se traduce así en el compilador:
string s;
while (enumerator.MoveNext())
{
s = enumerator.Current;
...
}
en lugar de esto:
while (enumerator.MoveNext())
{
string s;
s = enumerator.Current;
...
}
Como se señaló aquí , no hay ventajas de rendimiento para declarar una variable fuera del ciclo, y en circunstancias normales, la única razón que puedo pensar para hacerlo es si planea usar la variable fuera del alcance del ciclo:
string s;
while (enumerator.MoveNext())
{
s = enumerator.Current;
...
}
var finalString = s;
Sin embargo, las variables definidas en un foreach
bucle no pueden usarse fuera del bucle:
foreach(string s in strings)
{
}
var finalString = s; // won't work: you're outside the scope.
Por lo tanto, el compilador declara la variable de una manera que lo hace muy propenso a un error que a menudo es difícil de encontrar y depurar, sin producir beneficios perceptibles.
¿Hay algo que puede hacer con los foreach
bucles de esta manera que no podría si se compilaran con una variable de alcance interno, o es solo una elección arbitraria que se hizo antes de que los métodos anónimos y las expresiones lambda estuvieran disponibles o fueran comunes, y que no ¿ha sido revisado desde entonces?
String s; foreach (s in strings) { ... }
?foreach
sino sobre expresiones lamda que resultan en un código similar al mostrado por el OP ...Respuestas:
Su crítica está completamente justificada.
Discuto este problema en detalle aquí:
Cerrar sobre la variable de bucle considerado dañino
El último. La especificación C # 1.0 en realidad no decía si la variable del bucle estaba dentro o fuera del cuerpo del bucle, ya que no había ninguna diferencia observable. Cuando se introdujo la semántica de cierre en C # 2.0, se tomó la decisión de colocar la variable del bucle fuera del bucle, de manera consistente con el bucle "for".
Creo que es justo decir que todos lamentan esa decisión. Esta es una de las peores "trampas" en C #, y vamos a tomar el cambio más reciente para solucionarlo. En C # 5, la variable de bucle foreach estará lógicamente dentro del cuerpo del bucle y, por lo tanto, los cierres obtendrán una copia nueva cada vez.
El
for
bucle no se cambiará y el cambio no se "trasladará" a versiones anteriores de C #. Por lo tanto, debe seguir teniendo cuidado al usar este idioma.fuente
foreach
es 'seguro' perofor
no lo es.Lo que estás preguntando está completamente cubierto por Eric Lippert en su publicación de blog Cierre sobre la variable de bucle considerada dañina y su secuela.
Para mí, el argumento más convincente es que tener una nueva variable en cada iteración sería inconsistente con el
for(;;)
bucle de estilo. ¿Esperarías tener una nuevaint i
en cada iteración defor (int i = 0; i < 10; i++)
?El problema más común con este comportamiento es cerrar la variable de iteración y tiene una solución fácil:
Mi publicación de blog sobre este tema: Cierre sobre la variable foreach en C # .
fuente
ref
.cut
para referencias / vars ycute
para mantener los valores evaluados en cierres parcialmente evaluados).Habiendo sido mordido por esto, tengo la costumbre de incluir variables definidas localmente en el ámbito más interno que utilizo para transferir a cualquier cierre. En tu ejemplo:
Hago:
Una vez que tenga ese hábito, puede evitarlo en el caso muy raro en el que realmente pretendía unirse a los ámbitos externos. Para ser sincero, no creo haberlo hecho nunca.
fuente
En C # 5.0, este problema se soluciona y puede cerrar las variables de bucle y obtener los resultados que espera.
La especificación del lenguaje dice:
fuente