2009-01-16 6 views
18

He estado aprendiendo C#, y estoy tratando de entender lambdas. En esta muestra a continuación, imprime 10 diez veces.¿Cómo decirle a una función lambda que capture una copia en lugar de una referencia en C#?

class Program 
{ 
    delegate void Action(); 
    static void Main(string[] args) 
    { 
     List<Action> actions = new List<Action>(); 

     for (int i = 0; i < 10; ++i) 
      actions.Add(()=>Console.WriteLine(i)); 

     foreach (Action a in actions) 
      a(); 
    } 
} 

Obviamente, la clase generada detrás del lambda está almacenando una referencia o puntero a la variable int i, y es asignar un nuevo valor a la misma referencia cada vez que se repite el bucle. ¿Hay una manera de forzar la lambda para tomar una copia en su lugar, al igual que la sintaxis de C++ 0x

[&](){ ... } // Capture by reference 

vs

[=](){ ... } // Capture copies 
+0

Usted puede quiero leer [este artículo] (http://csharpindepth.com/Articles/Chapter5/Closures.aspx), escrito por nuestro propio Jon Skeet. –

+0

posible duplicado de [C# Variable capturada en bucle] (http://stackoverflow.com/questions/271440/c-sharp-captured-variable-in-loop) – nawfal

+0

Me resulta curioso que la mayoría de las respuestas a esta pregunta son explicando la semántica de captura que es perfectamente clara para el autor de la pregunta, mientras que solo algunos mencionan la solución (copia temporal). ¿Alguien leyó preguntas antes de responder? – ghord

Respuesta

18

Lo que el compilador está haciendo es extraer su lambda y cualquier variable capturada por la lambda en un compilador generado clase anidada.

Después de compilar el ejemplo se parece mucho a esto:

class Program 
{ 
     delegate void Action(); 
     static void Main(string[] args) 
     { 
       List<Action> actions = new List<Action>(); 

       DisplayClass1 displayClass1 = new DisplayClass1(); 
       for (displayClass1.i = 0; displayClass1.i < 10; ++displayClass1.i) 
         actions.Add(new Action(displayClass1.Lambda)); 

       foreach (Action a in actions) 
         a(); 
     } 

     class DisplayClass1 
     { 
       int i; 
       void Lambda() 
       { 
         Console.WriteLine(i); 
       } 
     } 
} 

Al hacer una copia dentro del bucle, el compilador genera nuevos objetos en cada iteración, así:

for (int i = 0; i < 10; ++i) 
{ 
    DisplayClass1 displayClass1 = new DisplayClass1(); 
    displayClass1.i = i; 
    actions.Add(new Action(displayClass1.Lambda)); 
} 
7

La única solución que he podido encontrar es hacer una copia local en primer lugar:

for (int i = 0; i < 10; ++i) 
{ 
    int copy = i; 
    actions.Add(() => Console.WriteLine(copy)); 
} 

pero estoy teniendo problemas para entender por qué poner una copia en el interior del bucle de alguna diferente de tener la captura lambda i.

+9

Porque la declaración de int está dentro del ciclo for, por lo que se vuelve a crear todo el tiempo. Hay 10 entradas diferentes, todas llamadas "copiar", donde solo hay una int llamada "i", dentro del alcance que se curry. – technophile

9

La única solución es hacer una copia local y hacer referencia a eso dentro de la lambda. Todas las variables en C# (y VB.Net) cuando se accede en un cierre tendrán semántica de referencia vs. semántica de copia/valor. No hay forma de cambiar este comportamiento en ninguno de los dos idiomas.

Nota: en realidad no se compila como referencia. El compilador eleva la variable a una clase de cierre y redirige los accesos de "i" a un campo "i" dentro de la clase de cierre dada. Sin embargo, a menudo es más fácil considerarlo como una semántica de referencia.

1

Recuerde que las expresiones lambda son realmente solo azúcar sintáctica para métodos anónimos.

Dicho esto, lo que realmente está buscando es cómo los métodos anónimos usan variables locales en un ámbito principal.

Aquí hay un enlace que describe esto. http://www.codeproject.com/KB/cs/InsideAnonymousMethods.aspx#4

Cuestiones relacionadas