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




where lambda c# (4)

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 ) .

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 mi opinión es una pregunta extraña. Es bueno saber cómo funciona el compilador, pero eso solo es "bueno saberlo".

Si escribe un código que depende del algoritmo del compilador, es una mala práctica. Y es mejor volver a escribir el código para excluir esta dependencia.

Esta es una buena pregunta para la entrevista de trabajo. Pero en la vida real no he enfrentado ningún problema que resolviera en una entrevista de trabajo.

El 90% de los usos de foreach para procesar cada elemento de la colección (no para seleccionar o calcular algunos valores). a veces es necesario calcular algunos valores dentro del bucle, pero no es una buena práctica crear un bucle GRANDE.

Es mejor usar expresiones LINQ para calcular los valores. Porque cuando calculas muchas cosas dentro del bucle, después de 2-3 meses cuando (o cualquier otra persona) lea este código, la persona no entenderá qué es esto y cómo debería funcionar.


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