Domanda

Supponi il seguente codice:

foreach(Item i on ItemCollection)
{
   Something s = new Something();
   s.EventX += delegate { ProcessItem(i); };
   SomethingCollection.Add(s);
}

Ovviamente, questo è sbagliato perché tutti i delegati puntano allo stesso oggetto. L'alternativa è:

foreach(Item i on ItemCollection)
{
   Item tmpItem = i;
   Something s = new Something();
   s.EventX += delegate { ProcessItem(tmpItem); };
   SomethingCollection.Add(s);
}

In questo caso tutti i delegati puntano al proprio Articolo.

E questo approccio? C'è qualche altra soluzione migliore?

È stato utile?

Soluzione

AGGIORNAMENTO: Vi sono approfondite analisi e commenti su questo problema qui:

http: //ericlippert.com/2009/11/12/closing-over-the-loop-variable-considered-harmful-part-one/


Questo è un problema segnalato molto frequentemente; di solito viene segnalato come un bug del compilatore, ma in realtà il compilatore sta facendo la cosa giusta in base alle specifiche. Le funzioni anonime si chiudono su variabili , non su valori e esiste una sola variabile loop foreach. Pertanto ogni lambda si chiude sulla stessa variabile e quindi ottiene il valore corrente di quella variabile.

Questo è sorprendente per quasi tutti e crea molta confusione e molte segnalazioni di bug. Stiamo considerando di modificare le specifiche e l'implementazione per un'ipotetica versione futura di C # in modo che la variabile loop sia dichiarata logicamente all'interno del costrutto loop, dando un "nuovo" variabile ogni volta attraverso il ciclo.

Questo sarebbe un cambiamento radicale , ma sospetto che il numero di persone che dipendono da questo strano comportamento sia piuttosto basso. Se hai opinioni su questo argomento, sentiti libero di aggiungere commenti ai post del blog citati nell'aggiornamento sopra. Grazie!

Altri suggerimenti

Il secondo blocco di codice è proprio l'approccio migliore in cui puoi mantenere tutte le altre cose allo stesso modo.

Tuttavia, potrebbe essere possibile creare una proprietà su Something che accetta Item . A sua volta, il codice evento potrebbe accedere a questo Item al largo del mittente dell'evento oppure potrebbe essere incluso negli eventi per l'evento. Quindi eliminando la necessità della chiusura.

Personalmente ho aggiunto " Eliminazione della chiusura non necessaria " come utile refactoring poiché può essere difficile ragionare su di essi.

Se ItemCollection è un (generico) Elenco puoi utilizzare è il metodo ForEach . Ti darà un nuovo ambito per i:

ItemCollection.ForEach(
    i =>
    {
        Something s = new Something();
        s.EventX += delegate { ProcessItem(i); };
        SomethingCollection.Add(s);
    });

Oppure puoi usare qualsiasi altro Linq -method adatto - come Seleziona :

var somethings = ItemCollection.Select(
        i =>
        {
            Something s = new Something();
            s.EventX += delegate { ProcessItem(i); };
            return s;
        });
foreach(Something s in somethings)
    SomethingCollection.Add(s);

Il problema che stai affrontando qui è legato a un costrutto linguistico come chiusura . Il secondo pezzo di codice risolve il problema.

foreach(Item i on ItemCollection)
{
   Something s = new Something(i);
   s.EventX += (sender, eventArgs) => { ProcessItem(eventArgs.Item);};
   SomethingCollection.Add(s);
}

non passeresti semplicemente 'i' nella tua classe 'Something' e la useresti negli argomenti dell'evento di EventX

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top