tutorial - where lambda c#




¿Hay alguna razón para que C#reutilice la variable en un foreach? (4)

El compilador declara la variable de una manera que lo hace altamente propenso a un error que a menudo es difícil de encontrar y depurar, mientras que no produce beneficios perceptibles.

Su crítica está totalmente justificada.

Discuto este problema en detalle aquí:

Cierre sobre la variable de bucle considerada dañina.

¿Hay algo que pueda hacer con los bucles foreach de esta manera que no podría hacer si se compilaran con una variable de ámbito interno? ¿o es solo una elección arbitraria que se realizó antes de que los métodos anónimos y las expresiones lambda estuvieran disponibles o fueran comunes, y que no se hayan revisado desde entonces?

El último. La especificación C # 1.0 en realidad no dijo si la variable del bucle estaba dentro o fuera del cuerpo del bucle, ya que no hizo una diferencia observable. Cuando se introdujo la semántica de cierre en C # 2.0, se tomó la decisión de colocar la variable de bucle fuera del bucle, de manera consistente con el bucle "for".

Creo que es justo decir que todos lamentan esa decisión. Este es uno de los peores "errores" en C #, y vamos a tomar el cambio decisivo 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 bucle for no se modificará, y el cambio no se "adaptará" a las versiones anteriores de C #. Por lo tanto, debe seguir teniendo cuidado al utilizar este idioma.

Cuando se utilizan expresiones lambda o métodos anónimos en C #, debemos tener cuidado con el acceso a la trampa de cierre modificada . 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 cláusulas Where en la consulta se basen en el valor final de s .

Como se explica here , esto sucede porque la variable s declarada en el bucle foreach anterior se traduce así en el compilador:

string s;
while (enumerator.MoveNext())
{
   s = enumerator.Current;
   ...
}

en lugar de como esto:

while (enumerator.MoveNext())
{
   string s;
   s = enumerator.Current;
   ...
}

Como se señaló here , no hay ventajas de rendimiento al declarar una variable fuera del bucle, y en circunstancias normales, la única razón que se me ocurre es si planea usar la variable fuera del alcance del bucle:

string s;
while (enumerator.MoveNext())
{
   s = enumerator.Current;
   ...
}
var finalString = s;

Sin embargo, las variables definidas en un bucle foreach 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 altamente propenso a un error que a menudo es difícil de encontrar y depurar, mientras que no produce beneficios perceptibles.

¿Hay algo que pueda hacer con los bucles foreach esta manera que no podría hacer si se compilaran con una variable de ámbito interno, o es solo una elección arbitraria que se realizó 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?


En C # 5.0, este problema está solucionado y puede cerrar las variables de bucle y obtener los resultados que espera.

La especificación del lenguaje dice:

8.8.4 La declaración foreach

(...)

Una declaración foreach del formulario.

foreach (V v in x) embedded-statement

luego se expande a:

{
  E e = ((C)(x)).GetEnumerator();
  try {
      while (e.MoveNext()) {
          V v = (V)(T)e.Current;
          embedded-statement
      }
  }
  finally {
      … // Dispose e
  }
}

(...)

La ubicación de v dentro del bucle while es importante por cómo es capturada por cualquier función anónima que ocurra en la declaración incrustada. Por ejemplo:

int[] values = { 7, 9, 13 };
Action f = null;
foreach (var value in values)
{
    if (f == null) f = () => Console.WriteLine("First value: " + value);
}
f();

Si v fuera declarado fuera del bucle while, se compartiría entre todas las iteraciones, y su valor después del bucle for sería el valor final, 13 , que es lo que se imprimirá la invocación de f . En cambio, como cada iteración tiene su propia variable v , la capturada por f en la primera iteración continuará manteniendo el valor 7 , que es lo que se imprimirá. ( Nota: las versiones anteriores de C # se declararon v fuera del bucle while ) .


Habiendo sido mordido por esto, tengo el hábito de incluir variables definidas localmente en el ámbito más interno que utilizo para transferir a cualquier cierre. En tu ejemplo:

foreach (var s in strings)
{
    query = query.Where(i => i.Prop == s); // access to modified closure

Hago:

foreach (var s in strings)
{
    string search = s;
    query = query.Where(i => i.Prop == search); // New definition ensures unique per iteration.

Una vez que tenga ese hábito, puede evitarlo en el muy raro caso en el que realmente pretendía vincularse a los ámbitos externos. Para ser honesto, no creo que lo haya hecho nunca.


Lo que estás pidiendo está cubierto completamente por Eric Lippert en su 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 bucle de estilo for(;;) . ¿Esperaría tener un nuevo int i en cada iteración de for (int i = 0; i < 10; i++) ?

El problema más común con este comportamiento es hacer un cierre sobre la variable de iteración y tiene una solución fácil:

foreach (var s in strings)
{
    var s_for_closure = s;
    query = query.Where(i => i.Prop == s_for_closure); // access to modified closure

Mi publicación de blog sobre este tema: Cierre sobre la variable foreach en C # .





anonymous-methods