switch - salir de if c#
Variable capturada en un bucle en C# (6)
Creo que lo que estás experimentando es algo conocido como Closure http://en.wikipedia.org/wiki/Closure_(computer_science) . Su lamba tiene una referencia a una variable que tiene un alcance fuera de la función en sí. Tu lamba no se interpreta hasta que lo invoques y, una vez que lo consiga, obtendrá el valor que tiene la variable en el momento de la ejecución.
Me encontré con un problema interesante sobre C #. Tengo un código como a continuación.
List<Func<int>> actions = new List<Func<int>>();
int variable = 0;
while (variable < 5)
{
actions.Add(() => variable * 2);
++ variable;
}
foreach (var act in actions)
{
Console.WriteLine(act.Invoke());
}
Espero que produzca 0, 2, 4, 6, 8. Sin embargo, en realidad produce cinco 10s.
Parece que se debe a todas las acciones que se refieren a una variable capturada. Como resultado, cuando se invocan, todos tienen la misma salida.
¿Hay alguna manera de trabajar alrededor de este límite para que cada instancia de acción tenga su propia variable capturada?
Detrás de escena, el compilador está generando una clase que representa el cierre de su llamada de método. Utiliza esa única instancia de la clase de cierre para cada iteración del ciclo. El código se ve más o menos así, lo que hace que sea más fácil ver por qué ocurre el error:
void Main()
{
List<Func<int>> actions = new List<Func<int>>();
int variable = 0;
var closure = new CompilerGeneratedClosure();
Func<int> anonymousMethodAction = null;
while (closure.variable < 5)
{
if(anonymousMethodAction == null)
anonymousMethodAction = new Func<int>(closure.YourAnonymousMethod);
//we''re re-adding the same function
actions.Add(anonymousMethodAction);
++closure.variable;
}
foreach (var act in actions)
{
Console.WriteLine(act.Invoke());
}
}
class CompilerGeneratedClosure
{
public int variable;
public int YourAnonymousMethod()
{
return this.variable * 2;
}
}
Este no es realmente el código compilado de su muestra, pero he examinado mi propio código y esto se parece mucho a lo que el compilador realmente generaría.
La forma de evitar esto es almacenar el valor que necesita en una variable proxy, y hacer que esa variable sea capturada.
ES DECIR
while( variable < 5 )
{
int copy = variable;
actions.Add( () => copy * 2 );
++variable;
}
La misma situación está sucediendo en multi-threading (C #, .NET 4.0).
Vea el siguiente código:
El propósito es imprimir 1,2,3,4,5 en orden.
for (int counter = 1; counter <= 5; counter++)
{
new Thread (() => Console.Write (counter)).Start();
}
¡La salida es interesante! (Podría ser como 21334 ...)
La única solución es usar variables locales.
for (int counter = 1; counter <= 5; counter++)
{
int localVar= counter;
new Thread (() => Console.Write (localVar)).Start();
}
Sí, necesita una variable
ámbito dentro del ciclo y pasarlo a la lambda de esa manera:
List<Func<int>> actions = new List<Func<int>>();
int variable = 0;
while (variable < 5)
{
int variable1 = variable;
actions.Add(() => variable1 * 2);
++variable;
}
foreach (var act in actions)
{
Console.WriteLine(act.Invoke());
}
Console.ReadLine();
Sí, tome una copia de la variable dentro del ciclo:
while (variable < 5)
{
int copy = variable;
actions.Add(() => copy * 2);
++ variable;
}
Puede pensar que el compilador de C # crea una variable local "nueva" cada vez que golpea la declaración de variable. De hecho, creará nuevos objetos de cierre apropiados, y se complica (en términos de implementación) si se refiere a variables en múltiples ámbitos, pero funciona :)
Tenga en cuenta que una ocurrencia más común de este problema es usar for
o foreach
:
for (int i=0; i < 10; i++) // Just one variable
foreach (string x in foo) // And again, despite how it reads out loud
Consulte la sección 7.14.4.2 de la especificación C # 3.0 para obtener más detalles al respecto, y mi artículo sobre cierres tiene más ejemplos también.