2011-01-17 6 views
12

Très souvent, je besoin de quelque chose comme ça:Meilleure pratique pour éviter InvalidOperationException: la collection a été modifiée?

foreach (Line line in lines) 
{ 
    if (line.FullfilsCertainConditions()) 
    { 
     lines.Remove(line) 
    } 
} 

Cela ne fonctionne pas, parce que je reçois toujours un InvalidOperationException parce que le recenseur a été changé au cours de la boucle.

Je changé toutes mes boucles de ce genre à ce qui suit:

List<Line> remove = new List<Line>(); 
foreach (Line line in lines) 
{ 
    if (line.FullfilsCertainConditions()) 
    { 
     remove.Add(line) 
    } 
} 

foreach (Line line in remove) { 
{ 
    lines.Remove(line); 
} 

Je ne sais pas si cela est vraiment la meilleure façon puisque dans le pire des cas, je dois itérer 2 fois l'original liste et donc il a besoin de temps 2n au lieu de n.

Y a-t-il une meilleure façon de procéder?

EDIT:

j'ai pu le faire en utilisant la réponse de Mark Mais si ma collection ne met en œuvre RemoveAll()?

Par exemple, un

System.Windows.Controls.UIElementCollection

EDIT 2:

Encore une fois, avec l'aide de Mark, je suis maintenant en mesure de faire l'appel suivant à supprimer tout ScatterViewItems:

CollectionUtils.RemoveAll(manager.getWindow().IconDisplay.Items, elem => elem.GetType() == typeof(ScatterViewItem)); 
+0

J'ai eu le même problème il y a quelque temps et pas de solution. C'est encore pire - ce n'est pas 2n mais n^2 puisque 'lines.Remove (line)' se répète sur la collection. – Matten

+0

Mais O (2n) est égal à O (n) :-) vraiment: En Java, vous pouvez utiliser un Iterator pour ce faire, ou opter pour une implémentation de Copy-on-Write Collection qui permet des modifications pendant l'itération. – Waldheinz

+0

Re la mise à jour - voir mon edit –

Répondre

17

Ceci est cuit directement ment en List<T>:

lines.RemoveAll(line => line.FullfilsCertainConditions()); 

ou en C# 2.0:

lines.RemoveAll(delegate(Line line) { 
    return line.FullfilsCertainConditions(); 
}); 

Dans le List<T> non cas (votre modification à la question), vous pouvez envelopper cette chose comme ci-dessous (non testé) :

static class CollectionUtils 
{ 
    public static void RemoveAll<T>(IList<T> list, Predicate<T> predicate) 
    { 
     int count = list.Count; 
     while (count-- > 0) 
     { 
      if (predicate(list[count])) list.RemoveAt(count); 
     } 
    } 
    public static void RemoveAll(IList list, Predicate<object> predicate) 
    { 
     int count = list.Count; 
     while (count-- > 0) 
     { 
      if (predicate(list[count])) list.RemoveAt(count); 
     } 
    } 
} 

Depuis UIElementCollection met en œuvre la (non générique) IList cela devrait fonctionner. Et très commodément, avec C# 3.0, vous pouvez ajouter un this avant IList/IList<T> et l'avoir comme une méthode d'extension. La seule subtilité est que le paramètre de la méthode anon sera object, donc vous aurez besoin de le rejeter.

+0

Y at-il un moyen d'atteindre la même chose dans .NET 2.0? – Matten

+2

@Matten - Ajout d'un exemple C# 2.0. –

+0

@Marc Gravell - très simple, très élégant. Merci :) – Matten

1

Vous pouvez remplacer simplement la liste originale avec une filtrée:

lines = lines.Where(line => line.FullfilsCertainConditions()).ToList(); 
1

construire une nouvelle liste instaed:

public IList<Line> GetListWithoutFullfilsCertainConditions(IList<Line> fullList) 
{ 
    IList<Line> resultList = new List<Line>(fullList.Count); 

    foreach (Line line in fullList) 
    { 
     if (!line.FullfilsCertainConditions()) 
     { 
      resultList.Add(line) 
     } 
    } 

    return resultList; 
} 
+0

Raison du vote négatif, s'il vous plaît? –

1

vous pouvez aussi utiliser en boucle.

int i = 0; 
while(i < lines.Count) 
{ 
    if (lines[i].FullfilsCertainConditions()) 
    { 
    lines.RemoveAt(i); 
    } 
    else {i++;} 
} 
Questions connexes