Pergunta sobre foreach e delegados
Pergunta
Suponha que o seguinte código:
foreach(Item i on ItemCollection)
{
Something s = new Something();
s.EventX += delegate { ProcessItem(i); };
SomethingCollection.Add(s);
}
Obviamente, isso está errado porque todos os delegados apontam para o mesmo item. A alternativa é:
foreach(Item i on ItemCollection)
{
Item tmpItem = i;
Something s = new Something();
s.EventX += delegate { ProcessItem(tmpItem); };
SomethingCollection.Add(s);
}
Nesse caso, todos os delegados apontam para o seu próprio item.
E essa abordagem? Existe alguma outra solução melhor?
Solução
ATUALIZAÇÃO: Há uma extensa análise e comentários sobre esta edição aqui:
http://ericlippert.com/2009/11/12/closing-over-the-loop-variable-concronsidered-harmful-part-one/
Este é um problema extremamente relatado; Geralmente é relatado como um bug do compilador, mas na verdade o compilador está fazendo a coisa certa de acordo com a especificação. Funções anônimas fecham variáveis, não valores, e há apenas uma única variável de loop de forach. Portanto, cada lambda fecha sobre a mesma variável e, portanto, obtém o valor atual dessa variável.
Isso é surpreendente para quase todos, e leva a muita confusão e muitos relatórios de bugs. Nós somos considerando Alterando a especificação e a implementação para uma versão futura hipotética do C#, para que a variável loop seja logicamente declarada dentro da construção de loop, fornecendo uma variável "nova" sempre através do loop.
Isso seria uma mudança de quebra, mas suspeito que o número de pessoas que dependem desse comportamento estranho é bastante baixo. Se você tiver opiniões sobre esse assunto, fique à vontade para adicionar comentários às postagens do blog mencionadas na atualização acima. Obrigado!
Outras dicas
A segunda parte do código é a melhor abordagem que você pode obter todas as outras coisas permanecendo iguais.
No entanto, pode ser possível criar uma propriedade em Something
que leva Item
. Por sua vez, o código do evento poderia acessar isso Item
Fora do remetente do evento ou pode ser incluído no EventArgs para o evento. Portanto, eliminando a necessidade do fechamento.
Pessoalmente, adicionei "a eliminação do fechamento desnecessário" como uma refatoração que vale a pena, pois pode ser difícil argumentar sobre eles.
Se o itemCollection for um Lista (genérica) você pode usar seu Para cada-método. Isso lhe dará um novo escopo por i:
ItemCollection.ForEach(
i =>
{
Something s = new Something();
s.EventX += delegate { ProcessItem(i); };
SomethingCollection.Add(s);
});
Ou você pode usar qualquer outro adequado Linq-Method - como Selecione:
var somethings = ItemCollection.Select(
i =>
{
Something s = new Something();
s.EventX += delegate { ProcessItem(i); };
return s;
});
foreach(Something s in somethings)
SomethingCollection.Add(s);
O problema que você está enfrentando aqui está relacionado a essa construção de linguagem como fecho. Segunda peça de código corrige o problema.
foreach(Item i on ItemCollection)
{
Something s = new Something(i);
s.EventX += (sender, eventArgs) => { ProcessItem(eventArgs.Item);};
SomethingCollection.Add(s);
}
Você não apenas passaria a 'eu' para a sua aula de 'algo' e usá -la no evento do Eventx Args