Question

Supposons le code suivant:

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

Bien sûr, cela est faux car tous les délégués pointent sur le même élément. L'alternative est:

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

Dans ce cas, tous les délégués désignent leur propre élément.

Qu'en est-il de cette approche? Il y a une autre meilleure solution?

Était-ce utile?

La solution

MISE À JOUR: Il existe une analyse et des commentaires approfondis sur cette question ici:

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

Il s'agit d'un problème extrêmement fréquemment signalé. généralement, il est signalé comme un bogue du compilateur, mais en fait, le compilateur fait ce qui est juste conformément à la spécification. Les fonctions anonymes se ferment sur variables et non sur valeurs , et il n’existe qu’une seule variable de boucle foreach. Par conséquent, chaque lambda se ferme sur la même variable et obtient donc la valeur actuelle de cette variable.

Cela surprend presque tout le monde et crée beaucoup de confusion et de nombreux rapports de bugs Nous envisageons de modifier les spécifications et l'implémentation d'une version future hypothétique de C # afin que la variable de boucle soit déclarée de manière logique à l'intérieur de la construction en boucle, ce qui donne un résultat "frais". variable à chaque fois dans la boucle.

Ce changement constituerait un changement radical , mais je soupçonne que le nombre de personnes qui dépendent de ce comportement étrange est assez faible. Si vous avez des opinions à ce sujet, n'hésitez pas à ajouter des commentaires aux articles de blog mentionnés dans la mise à jour ci-dessus. Merci!

Autres conseils

Le deuxième morceau de code est à peu près la meilleure approche pour que toutes les autres choses restent identiques.

Cependant, il peut être possible de créer une propriété sur quelque chose qui prend Item . À son tour, le code de l'événement peut accéder à cet élément de l'expéditeur de l'événement ou peut être inclus dans les arguments de l'événement. D’où la suppression de la nécessité de la fermeture.

Personnellement, j'ai ajouté "Suppression des fermetures inutiles". comme une refactorisation valable car il peut être difficile de les raisonner.

Si ItemCollection est une liste (générique) , vous pouvez l'utiliser. sa ForEach -method. Cela vous donnera une nouvelle portée par i:

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

Vous pouvez également utiliser tout autre Linq approprié à comme Sélectionnez :

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

Le problème que vous rencontrez ici est lié à une construction de langage telle que la clôture . Le deuxième morceau de code corrige le problème.

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

ne voudriez-vous pas simplement passer "i" dans votre classe "Something" et l'utiliser dans les arguments d'événement d'EventX

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top