2009-10-09 7 views
4

Pouvez-vous supprimer un élément d'une liste <> pendant l'itération? Est-ce que cela fonctionnera, ou y a-t-il une meilleure façon de le faire?Pouvez-vous supprimer un élément d'une liste <> tout en le parcourant en C#

Mon code:

foreach (var bullet in bullets) 
    { 
    if (bullet.Offscreen()) 
    { 
     bullets.Remove(bullet); 
    } 
    } 

-Edit- Désolé les gars, c'est un jeu de silverlight. Je ne savais pas que Silverlight était différent du Compact Framework.

+0

voir la question suivante: http: // stackoverflow.com/questions/308466/comment-modifier-ou-supprimer-items-d'-un-enumerable-collection-while-iterating-throu –

Répondre

10

Modifier: pour clarifier, la question concerne Silverlight, qui ne semble supporte pas RemoveAll on List`T. Il est disponible dans le full framework, CF, XNA versions 2.0+

Vous pouvez écrire un lambda qui exprime vos critères de suppression:

bullets.RemoveAll(bullet => bullet.Offscreen()); 

Vous pouvez également sélectionner ceux que vous ne voulez, au lieu de supprimer ceux que vous n'avez pas:

bullets = bullets.Where(b => !b.OffScreen()).ToList(); 

Ou utilisez le indexeur pour revenir en arrière dans la séquence:

for(int i=bullets.Count-1;i>=0;i--) 
{ 
    if(bullets[i].OffScreen()) 
    { 
     bullets.RemoveAt(i); 
    } 
} 
+0

Ceci est très inefficace par rapport à 'List .RemoveAll' (voir ma réponse). –

+0

@ 280z28 ah oui, oublié à propos de RemoveAll. –

+0

Votre seconde version me convient le mieux (reverse iterate). RemoveAll ne semble plus fonctionner (déconseillé?). – Chris

4

Une tentative de suppression dans une boucle foreach déclenchera une exception. Vous devez parcourir à l'envers avec une boucle for.

for (int count = bullets.Count - 1; count >= 0; count--) 
{ 
    if (bullets[count].Offscreen()) 
    { 
     //bullets.Remove(bullets[count]); 
     bullets.RemoveAt(count); 
    } 
} 
+0

Cela ressemble à une bonne idée, merci. Il n'y aura pas de pénalité liée à la performance en itérant en arrière dans la liste que vous pensez? – Chris

+0

pour les boucles sont généralement considérés comme rapides. Traverser en sens inverse ne devrait pas être très différent de le faire normalement. De plus, il n'y a pas de copie de liste temporaire, donc cela affecte la collection originale. –

+0

En outre, je pense que «supprimer» serait plus rapide que «supprimer» – Chris

2

Il est préférable de créer soit une liste qui contiendra des éléments à supprimer, puis supprimer des éléments de la liste:

List<Bullet> removedBullets = new List<Bullet>(); 

foreach(var bullet in bullets) 
{ 
    if (bullet.OffScreen()) 
    { 
    removedBullets.Add(bullet); 
    } 
} 

foreach(var bullet in removedBullets) 
{ 
    bullets.Remove(bullet); 
} 
+0

C'était ma pensée originale, mais puisque c'est un jeu en temps réel, je ne suis pas désireux de créer une nouvelle liste en raison des frais généraux possibles. – Chris

17
bullets.RemoveAll(bullet => bullet.Offscreen()); 

Edit: Pour faire ce travail-est Dans Silverlight, ajoutez la méthode d'extension suivante à votre projet.

Comme List<T>.RemoveAll, cet algorithme est O (N) où N est la longueur de la liste par opposition à O (N * M) où M est le nombre d'éléments supprimés de la liste. Comme c'est une méthode d'extension avec le même prototype que la méthode RemoveAll que l'on trouve dans les frameworks non-Silverlight, la méthode intégrée sera utilisée lorsqu'elle sera disponible, et celle-ci sera utilisée de manière transparente pour les builds Silverlight.

public static class ListExtensions 
{ 
    public static int RemoveAll<T>(this List<T> list, Predicate<T> match) 
    { 
     if (list == null) 
      throw new NullReferenceException(); 

     if (match == null) 
      throw new ArgumentNullException("match"); 

     int i = 0; 
     int j = 0; 

     for (i = 0; i < list.Count; i++) 
     { 
      if (!match(list[i])) 
      { 
       if (i != j) 
        list[j] = list[i]; 

       j++; 
      } 
     } 

     int removed = i - j; 
     if (removed > 0) 
      list.RemoveRange(list.Count - removed, removed); 

     return removed; 
    } 
} 
+0

J'adore! Grande réponse :) – Russell

+0

+1 vous me battre à elle! :) –

+0

C'EST la réponse, ce serait bien si tout le monde pouvait supprimer le leur ... – Benjol

1

I've come across this problem before and blogged about it here.

version courte est que vous pouvez créer une méthode d'extension appelée removeif:

public void RemoveIf<T>(ICollection<T> collection, Predicate<T> match) 
{ 
    List<T> removed = new List<T>(); 
    foreach (T item in collection) 
    { 
     if (match(item)) 
     { 
      removed.Add(item); 
     } 
    } 

    foreach (T item in removed) 
    { 
     collection.Remove(item); 
    } 

    removed.Clear(); 
} 

Et alors appeler avec votre délégué à chaque fois que vous en avez besoin:

RemoveIf(_Entities.Item, delegate(Item i) { return i.OffScreen(); }); 
2

Itérer dans « pour » boucle plutôt que d'itérer à travers foreach. Cela fonctionnera.

3

Essayez ceci:

bullets.RemoveAll(bullet => bullet.Offscreen()); 
Questions connexes