resolvidos - Variável capturada em um loop em c#




for loop c# (6)

Isso não tem nada a ver com loops.

Esse comportamento é acionado porque você usa uma expressão lambda () => variable * 2 que a variable escopo externo não é realmente definida no escopo interno do lambda.

Expressões lambda (em C # 3 +, bem como métodos anônimos em C # 2) ainda criam métodos reais. Passar variáveis ​​para esses métodos envolve alguns dilemas (passar por valor? Passar por referência? C # vai por referência - mas isso abre outro problema em que a referência pode sobreviver à variável real). O que o C # faz para resolver todos esses dilemas é criar uma nova classe auxiliar ("closure") com campos correspondentes às variáveis ​​locais usadas nas expressões lambda e métodos correspondentes aos métodos lambda reais. Quaisquer alterações na variable em seu código são traduzidas para alterar essa ClosureClass.variable

Então o seu loop while mantém atualizando o ClosureClass.variable até que ele atinja 10, então você para loops executa as ações, todas operando na mesma ClosureClass.variable .

Para obter o resultado esperado, você precisa criar uma separação entre a variável de loop e a variável que está sendo fechada. Você pode fazer isso introduzindo outra variável, ou seja:

List<Func<int>> actions = new List<Func<int>>();
int variable = 0;
while (variable < 5)
{
    var t = variable; // now t will be closured (i.e. replaced by a field in the new class)
    actions.Add(() => t * 2);
    ++variable; // changing variable won't affect the closured variable t
}
foreach (var act in actions)
{
    Console.WriteLine(act.Invoke());
}

Você também pode mover o fechamento para outro método para criar essa separação:

List<Func<int>> actions = new List<Func<int>>();

int variable = 0;
while (variable < 5)
{
    actions.Add(Mult(variable));
    ++variable;
}

foreach (var act in actions)
{
    Console.WriteLine(act.Invoke());
}

Você pode implementar Mult como uma expressão lambda (fechamento implícito)

static Func<int> Mult(int i)
{
    return () => i * 2;
}

ou com uma classe auxiliar real:

public class Helper
{
    public int _i;
    public Helper(int i)
    {
        _i = i;
    }
    public int Method()
    {
        return _i * 2;
    }
}

static Func<int> Mult(int i)
{
    Helper help = new Helper(i);
    return help.Method;
}

Em qualquer caso, "Closures" NÃO são um conceito relacionado a loops , mas sim a métodos anônimos / expressões lambda usando variáveis ​​de escopo local - embora alguns usos imprecisos de loops demonstrem traps de encerramento.

Eu encontrei uma questão interessante sobre o C #. Eu tenho código como abaixo.

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 ele produza 0, 2, 4, 6, 8. No entanto, ele realmente produz cinco 10s.

Parece que é devido a todas as ações referentes a uma variável capturada. Como resultado, quando são invocados, todos eles têm a mesma saída.

Existe uma maneira de contornar esse limite para que cada instância de ação tenha sua própria variável capturada?


É chamado de problema de fechamento, basta usar uma variável de cópia e pronto.

List<Func<int>> actions = new List<Func<int>>();

int variable = 0;
while (variable < 5)
{
    int i = variable;
    actions.Add(() => i * 2);
    ++ variable;
}

foreach (var act in actions)
{
    Console.WriteLine(act.Invoke());
}

A mesma situação está acontecendo em multi-threading (C #, .NET 4.0].

Veja o seguinte código:

O objetivo é imprimir 1,2,3,4,5 em ordem.

for (int counter = 1; counter <= 5; counter++)
{
    new Thread (() => Console.Write (counter)).Start();
}

A saída é interessante! (Pode ser como 21334 ...)

A única solução é usar variáveis ​​locais.

for (int counter = 1; counter <= 5; counter++)
{
    int localVar= counter;
    new Thread (() => Console.Write (localVar)).Start();
}

Eu acredito que o que você está experimentando é algo conhecido como Encerramento http://en.wikipedia.org/wiki/Closure_(computer_science) . Seu lamba tem uma referência a uma variável que está fora da função em si. Seu lamba não é interpretado até que você o chame e, uma vez que esteja, ele obterá o valor que a variável possui no tempo de execução.


Sim - tire uma cópia da variável dentro do loop:

while (variable < 5)
{
    int copy = variable;
    actions.Add(() => copy * 2);
    ++ variable;
}

Você pode pensar nisso como se o compilador C # criasse uma variável local "nova" sempre que atingisse a declaração de variável. Na verdade, ele criará novos objetos de fechamento apropriados e ficará complicado (em termos de implementação) se você se referir a variáveis ​​em vários escopos, mas funciona :)

Observe que uma ocorrência mais comum desse problema é usar for ou 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 a seção 7.14.4.2 da especificação do C # 3.0 para obter mais detalhes sobre isso, e meu artigo sobre fechamento também tem mais exemplos.


Sim, você precisa escopo variable dentro do loop e passá-lo para o lambda dessa maneira:

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();




captured-variable