J'ai le cas classique d'essayer de supprimer un élément d'une collection tout en l'énumérant dans une boucle:
List<int> myIntCollection = new List<int>();
myIntCollection.Add(42);
myIntCollection.Add(12);
myIntCollection.Add(96);
myIntCollection.Add(25);
foreach (int i in myIntCollection)
{
if (i == 42)
myIntCollection.Remove(96); // The error is here.
if (i == 25)
myIntCollection.Remove(42); // The error is here.
}
Au début de l'itération après une modification, un InvalidOperationException
est levé, car les énumérateurs n'aiment pas quand la collection sous-jacente change.
Je dois apporter des modifications à la collection lors de l'itération. Il existe de nombreux modèles qui peuvent être utilisés pour éviter cela , mais aucun d'entre eux ne semble avoir une bonne solution:
Ne supprimez pas à l'intérieur de cette boucle, conservez plutôt une «liste de suppression» séparée, que vous traitez après la boucle principale.
C'est normalement une bonne solution, mais dans mon cas, j'ai besoin que l'élément disparaisse instantanément car «attendre» qu'après la boucle principale pour vraiment supprimer l'élément change le flux logique de mon code.
Au lieu de supprimer l'élément, définissez simplement un indicateur sur l'élément et marquez-le comme inactif. Ajoutez ensuite la fonctionnalité du modèle 1 pour nettoyer la liste.
Ce serait travailler pour tous mes besoins, mais cela signifie que beaucoup de code à changer afin de vérifier le drapeau inactif chaque fois qu'un élément est accessible. C'est beaucoup trop d'administration à mon goût.
Incorporez d'une manière ou d'une autre les idées du modèle 2 dans une classe qui dérive de
List<T>
. Cette super liste gérera l'indicateur inactif, la suppression des objets après coup et n'exposera pas non plus les éléments marqués comme inactifs aux consommateurs d'énumération. Fondamentalement, il encapsule simplement toutes les idées du modèle 2 (et par la suite du modèle 1).Une classe comme celle-ci existe-t-elle? Quelqu'un a-t-il un code pour cela? Ou y a-t-il un meilleur moyen?
On m'a dit qu'accéder
myIntCollection.ToArray()
au lieu demyIntCollection
résoudrait le problème et me permettrait de supprimer à l'intérieur de la boucle.Cela me semble être un mauvais modèle de conception, ou peut-être que ça va?
Détails:
La liste contiendra de nombreux éléments et je n'en supprimerai que quelques-uns.
À l'intérieur de la boucle, je vais faire toutes sortes de processus, ajouter, supprimer, etc., donc la solution doit être assez générique.
L'élément que je dois supprimer n'est peut- être pas l'élément actuel de la boucle. Par exemple, je peux être sur l'élément 10 d'une boucle de 30 éléments et avoir besoin de supprimer l'élément 6 ou l'élément 26. Marcher en arrière dans le tableau ne fonctionnera plus à cause de cela. ; o (
la source
Réponses:
La meilleure solution est généralement d'utiliser la
RemoveAll()
méthode:myList.RemoveAll(x => x.SomeProp == "SomeValue");
Ou, si vous avez besoin de certains éléments supprimés:
MyListType[] elems = new[] { elem1, elem2 }; myList.RemoveAll(x => elems.Contains(x));
Cela suppose que votre boucle est uniquement destinée à des fins de suppression, bien sûr. Si vous avez besoin de traitement supplémentaire, la meilleure méthode est généralement d'utiliser un
for
ouwhile
boucle, car vous n'êtes pas en utilisant un recenseur:for (int i = myList.Count - 1; i >= 0; i--) { // Do processing here, then... if (shouldRemoveCondition) { myList.RemoveAt(i); } }
Revenir en arrière garantit que vous ne sautez aucun élément.
Réponse à la modification :
Si vous allez supprimer des éléments apparemment arbitraires, la méthode la plus simple peut être de simplement garder une trace des éléments que vous souhaitez supprimer, puis de les supprimer tous en même temps. Quelque chose comme ça:
List<int> toRemove = new List<int>(); foreach (var elem in myList) { // Do some stuff // Check for removal if (needToRemoveAnElement) { toRemove.Add(elem); } } // Remove everything here myList.RemoveAll(x => toRemove.Contains(x));
la source
Si vous devez à la fois énumérer un
List<T>
et le supprimer, je suggère simplement d'utiliser unewhile
boucle au lieu d'unforeach
var index = 0; while (index < myList.Count) { if (someCondition(myList[index])) { myList.RemoveAt(index); } else { index++; } }
la source
Je sais que cet article est ancien, mais j'ai pensé partager ce qui a fonctionné pour moi.
Créez une copie de la liste pour l'énumération, puis dans le pour chaque boucle, vous pouvez traiter les valeurs copiées et supprimer / ajouter / quoi que ce soit avec la liste source.
private void ProcessAndRemove(IList<Item> list) { foreach (var item in list.ToList()) { if (item.DeterminingFactor > 10) { list.Remove(item); } } }
la source
Lorsque vous avez besoin d'itérer une liste et que vous pouvez la modifier pendant la boucle, il vaut mieux utiliser une boucle for:
for (int i = 0; i < myIntCollection.Count; i++) { if (myIntCollection[i] == 42) { myIntCollection.Remove(i); i--; } }
Bien sûr il faut faire attention, par exemple je décrémente
i
chaque fois qu'un élément est supprimé, sinon nous sauterons des entrées (une alternative est de revenir en arrière dans la liste).Si vous avez Linq, utilisez simplement
RemoveAll
comme dlev l'a suggéré.la source
--i
.Au fur et à mesure que vous énumérez la liste, ajoutez celle que vous souhaitez GARDER dans une nouvelle liste. Ensuite, attribuez la nouvelle liste au
myIntCollection
List<int> myIntCollection=new List<int>(); myIntCollection.Add(42); List<int> newCollection=new List<int>(myIntCollection.Count); foreach(int i in myIntCollection) { if (i want to delete this) /// else newCollection.Add(i); } myIntCollection = newCollection;
la source
Ajoutons votre code:
List<int> myIntCollection=new List<int>(); myIntCollection.Add(42); myIntCollection.Add(12); myIntCollection.Add(96); myIntCollection.Add(25);
Si vous souhaitez modifier la liste pendant que vous êtes dans un foreach, vous devez taper
.ToList()
foreach(int i in myIntCollection.ToList()) { if (i == 42) myIntCollection.Remove(96); if (i == 25) myIntCollection.Remove(42); }
la source
Pour ceux que cela peut aider, j'ai écrit cette méthode d'extension pour supprimer les éléments correspondant au prédicat et renvoyer la liste des éléments supprimés.
public static IList<T> RemoveAllKeepRemoved<T>(this IList<T> source, Predicate<T> predicate) { IList<T> removed = new List<T>(); for (int i = source.Count - 1; i >= 0; i--) { T item = source[i]; if (predicate(item)) { removed.Add(item); source.RemoveAt(i); } } return removed; }
la source
Que diriez-vous
int[] tmp = new int[myIntCollection.Count ()]; myIntCollection.CopyTo(tmp); foreach(int i in tmp) { myIntCollection.Remove(42); //The error is no longer here. }
la source
foreach (int i in myIntCollection.ToArray()) { myIntCollection.Remove(42); }
pour n'importe quel énumérable etList<T>
prend spécifiquement en charge cette méthode, même dans .NET 2.0.Si vous êtes intéressé par la haute performance, vous pouvez utiliser deux listes. Ce qui suit minimise le garbage collection, maximise la localité de la mémoire et ne supprime jamais réellement un élément d'une liste, ce qui est très inefficace s'il ne s'agit pas du dernier élément.
private void RemoveItems() { _newList.Clear(); foreach (var item in _list) { item.Process(); if (!item.NeedsRemoving()) _newList.Add(item); } var swap = _list; _list = _newList; _newList = swap; }
la source
Je viens de penser que je vais partager ma solution à un problème similaire où je devais supprimer des éléments d'une liste tout en les traitant.
Donc, fondamentalement, "foreach" supprimera l'élément de la liste une fois qu'il a été itéré.
Mon test:
var list = new List<TempLoopDto>(); list.Add(new TempLoopDto("Test1")); list.Add(new TempLoopDto("Test2")); list.Add(new TempLoopDto("Test3")); list.Add(new TempLoopDto("Test4")); list.PopForEach((item) => { Console.WriteLine($"Process {item.Name}"); }); Assert.That(list.Count, Is.EqualTo(0));
J'ai résolu cela avec une méthode d'extension "PopForEach" qui effectuera une action puis supprimera l'élément de la liste.
public static class ListExtensions { public static void PopForEach<T>(this List<T> list, Action<T> action) { var index = 0; while (index < list.Count) { action(list[index]); list.RemoveAt(index); } } }
J'espère que cela peut être utile à n'importe qui.
la source