Como contar uma função lambda para capturar uma cópia em vez de uma referência em C #?
Pergunta
Fui aprendendo C #, e eu estou tentando entender lambdas. Neste exemplo abaixo, ele imprime 10 de dez vezes.
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, a classe gerada por trás do lambda está armazenando uma referência ou um ponteiro para a variável int i
, e é a atribuição de um novo valor para a mesma referência de cada vez que o itera ansa. Existe uma maneira de forçar o lambda para pegar uma cópia em vez disso, como o C ++ 0x sintaxe
[&](){ ... } // Capture by reference
vs.
[=](){ ... } // Capture copies
Solução
O que o compilador está fazendo está puxando o seu lambda e quaisquer variáveis ??capturado pela lambda em um compilador gerado classe aninhada.
Após a compilação seu exemplo parece muito com isto:
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);
}
}
}
Ao fazer uma cópia dentro do loop, o compilador gera novos objetos em cada iteração, assim:
for (int i = 0; i < 10; ++i)
{
DisplayClass1 displayClass1 = new DisplayClass1();
displayClass1.i = i;
actions.Add(new Action(displayClass1.Lambda));
}
Outras dicas
A única solução que eu fui capaz de encontrar é fazer uma cópia local em primeiro lugar:
for (int i = 0; i < 10; ++i)
{
int copy = i;
actions.Add(() => Console.WriteLine(copy));
}
Mas eu estou tendo dificuldade para entender por que colocar uma cópia dentro do loop for é diferente do que ter o i
captura lambda.
A única solução é fazer uma cópia local e referência que dentro do lambda. Todas as variáveis ??em C # (e VB.Net) quando acedida em um fecho terá semântica de referência versus semântica cópia / valor. Não há nenhuma maneira de mudar esse comportamento em qualquer idioma.
Nota: Não realmente compilar como uma referência. Os monta-cargas de compilador a variável em uma classe de fecho e redireccionamentos acessos de "I" em um campo de "i" dentro da classe dada fecho. É muitas vezes mais fácil pensar nisso como semântica de referência embora.
Lembre-se que as expressões lambda são o açúcar realmente só sintático para métodos anônimos.
Dito isto, o que você está procurando realmente é como métodos anônimos usar variáveis ??locais em um escopo pai.
Aqui está um link que descreve este. http://www.codeproject.com/KB/cs/InsideAnonymousMethods.aspx# 4