2010-07-02 8 views
21

Considere el siguiente código:Usando la variable del iterador del bucle foreach en una expresión lambda, ¿por qué falla?

public class MyClass 
{ 
    public delegate string PrintHelloType(string greeting); 


    public void Execute() 
    { 

     Type[] types = new Type[] { typeof(string), typeof(float), typeof(int)}; 
     List<PrintHelloType> helloMethods = new List<PrintHelloType>(); 

     foreach (var type in types) 
     { 
      var sayHello = 
       new PrintHelloType(greeting => SayGreetingToType(type, greeting)); 
      helloMethods.Add(sayHello); 
     } 

     foreach (var helloMethod in helloMethods) 
     { 
      Console.WriteLine(helloMethod("Hi")); 
     } 

    } 

    public string SayGreetingToType(Type type, string greetingText) 
    { 
     return greetingText + " " + type.Name; 
    } 

... 

} 

Después de llamar myClass.Execute(), el código imprime la siguiente respuesta inesperada:

 
Hi Int32 
Hi Int32 
Hi Int32 

Obviamente, yo esperaría "Hi String", "Hi Single", "Hi Int32", pero al parecer no es el caso. ¿Por qué el último elemento de la matriz iterada se está utilizando en los 3 métodos en lugar del apropiado?

¿Cómo reescribirías el código para lograr el objetivo deseado?

+0

Ni siquiera leí la pregunta, pero por el título, sé que la respuesta es: http://lorgonblog.spaces.live.com/blog/cns!701679AD17B6D310!689.entry – Brian

+0

La variable variable capturada diariamente levanta su fea cabeza. – Marc

Respuesta

28

Bienvenido al mundo de los cierres y las variables capturadas :)

Eric Lippert tiene una explicación en profundidad de este comportamiento:

básicamente, es la variable de bucle que se captura, no su valor. para conseguir lo que piensa que debe recibir, hace esto:

foreach (var type in types) 
{ 
    var newType = type; 
    var sayHello = 
      new PrintHelloType(greeting => SayGreetingToType(newType, greeting)); 
    helloMethods.Add(sayHello); 
} 
+4

La baliza @Eric Lippert se ha encendido. –

+3

No hay más dios que Anders, y Eric es su profeta :) – SWeko

+0

Podría agregar que esto puede atrapar incluso a aquellos bien versados ​​en cierres desprevenidos: Lua, y probablemente otros idiomas, tienen el "tipo" léxicamente dentro del ciclo soportes. Entonces en Lua aún capturas la variable, pero es una variable nueva en cada iteración. Esto es algo que cuando programo en Lua lo usas todo el tiempo, pero en mis años de programación en C# todavía tengo que escribir un método que se beneficie de su alcance de 'tipo' -es-fuera-de-los-corchetes . ¿Alguien tiene? – Mania

3

se puede arreglar mediante la introducción variable adicional:

... 
foreach (var type in types) 
     { 
      var t = type; 
      var sayHello = new PrintHelloType(greeting => SayGreetingToType(t, greeting)); 
      helloMethods.Add(sayHello); 
     } 
.... 
5

Como una breve explicación que alude a las publicaciones en blogs que Sweko referencia, una lambda está capturando la variable , no el valor . En un bucle foreach, la variable no es única en cada iteración, la misma variable se usa durante la duración del bucle (esto es más obvio cuando se ve la expansión que el compilador realiza en el foreach en tiempo de compilación). Como resultado, ha capturado la misma variable durante cada iteración, y la variable (a partir de la última iteración) se refiere al último elemento de su conjunto.

Actualización: En las nuevas versiones de la lengua (a partir de C 5 #), la variable de bucle se considera nueva con cada iteración, por lo que el cierre sobre ella no produce el mismo problema como lo hizo en las versiones anteriores (C# 4 y previo).

Cuestiones relacionadas