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?

Foi útil?

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

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top