Ich habe den klassischen Fall, dass versucht wird, ein Element aus einer Sammlung zu entfernen, während es in einer Schleife aufgelistet wird:
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.
}
Zu Beginn der Iteration nach einer Änderung wird ein InvalidOperationException
ausgelöst, da Enumeratoren nicht mögen, wenn sich die zugrunde liegende Sammlung ändert.
Ich muss während der Iteration Änderungen an der Sammlung vornehmen. Es gibt viele Muster, die verwendet werden können, um dies zu vermeiden , aber keines von ihnen scheint eine gute Lösung zu haben:
Löschen Sie nicht innerhalb dieser Schleife, sondern führen Sie eine separate „Löschliste“, die Sie nach der Hauptschleife verarbeiten.
Dies ist normalerweise eine gute Lösung, aber in meinem Fall muss das Element sofort als "Warten" verschwunden sein, bis nach der Hauptschleife, um das Element wirklich zu löschen, der Logikfluss meines Codes geändert wird.
Anstatt das Element zu löschen, setzen Sie einfach ein Flag auf das Element und markieren Sie es als inaktiv. Fügen Sie dann die Funktionalität von Muster 1 hinzu, um die Liste zu bereinigen.
Dies würde für alle meine Anforderungen funktionieren, bedeutet jedoch, dass sich bei jedem Zugriff auf ein Element viel Code ändern muss, um das inaktive Flag zu überprüfen. Dies ist viel zu viel Verwaltung für meinen Geschmack.
Integrieren Sie die Ideen von Muster 2 irgendwie in eine Klasse, die von abgeleitet ist
List<T>
. Diese Superliste behandelt das inaktive Flag, das Löschen von Objekten nachträglich und macht auch keine als inaktiv gekennzeichneten Elemente für Aufzählungskonsumenten verfügbar. Grundsätzlich werden nur alle Ideen von Muster 2 (und anschließend von Muster 1) zusammengefasst.Gibt es eine solche Klasse? Hat jemand Code dafür? Oder gibt es einen besseren Weg?
Mir wurde gesagt, dass der Zugriff auf
myIntCollection.ToArray()
anstelle vonmyIntCollection
das Problem löst und es mir ermöglicht, innerhalb der Schleife zu löschen.Dies scheint mir ein schlechtes Designmuster zu sein, oder ist es in Ordnung?
Einzelheiten:
Die Liste wird viele Elemente enthalten und ich werde nur einige davon entfernen.
Innerhalb der Schleife werde ich alle Arten von Prozessen ausführen, hinzufügen, entfernen usw., daher muss die Lösung ziemlich allgemein sein.
Das Element, das ich löschen muss, ist möglicherweise nicht das aktuelle Element in der Schleife. Zum Beispiel kann ich mich auf Punkt 10 einer 30-Punkte-Schleife befinden und muss Punkt 6 oder Punkt 26 entfernen. Aus diesem Grund funktioniert es nicht mehr, rückwärts durch das Array zu gehen. ;Ö(
quelle
Antworten:
Die beste Lösung ist normalerweise die Verwendung der folgenden
RemoveAll()
Methode:Oder wenn Sie bestimmte Elemente entfernen müssen:
Dies setzt natürlich voraus, dass Ihre Schleife ausschließlich zum Entfernen bestimmt ist. Wenn Sie tun müssen , um eine zusätzliche Verarbeitung, dann ist die beste Methode , in der Regel eine zu verwenden
for
oderwhile
Schleife, da dann sind Sie nicht einen Enumerator mit:Wenn Sie rückwärts gehen, wird sichergestellt, dass Sie keine Elemente überspringen.
Antwort auf Bearbeiten :
Wenn scheinbar willkürliche Elemente entfernt werden sollen, besteht die einfachste Methode darin, nur die Elemente zu verfolgen, die Sie entfernen möchten, und sie anschließend alle auf einmal zu entfernen. Etwas wie das:
quelle
Wenn Sie sowohl a aufzählen als auch daraus
List<T>
entfernen müssen, empfehle ich, einfach einewhile
Schleife anstelle von a zu verwendenforeach
quelle
Ich weiß, dass dieser Beitrag alt ist, aber ich dachte, ich würde mitteilen, was für mich funktioniert hat.
Erstellen Sie eine Kopie der Liste zum Aufzählen, und dann können Sie in jeder Schleife die kopierten Werte verarbeiten und mit der Quellliste entfernen / hinzufügen / was auch immer.
quelle
Wenn Sie eine Liste durchlaufen müssen und sie möglicherweise während der Schleife ändern, ist es besser, eine for-Schleife zu verwenden:
Natürlich müssen Sie vorsichtig sein, zum Beispiel dekrementiere ich,
i
wenn ein Element entfernt wird, da wir sonst Einträge überspringen (eine Alternative besteht darin, die Liste rückwärts durchzugehen).Wenn Sie Linq haben, sollten Sie es einfach so verwenden,
RemoveAll
wie es dlev vorgeschlagen hat.quelle
--i
.Fügen Sie beim Auflisten der Liste die Liste hinzu, die Sie behalten möchten, zu einer neuen Liste. Weisen Sie anschließend die neue Liste der zu
myIntCollection
quelle
Fügen wir Ihren Code hinzu:
Wenn Sie die Liste ändern möchten, während Sie sich in einem Foreach befinden, müssen Sie Folgendes eingeben
.ToList()
quelle
Wie wäre es mit
quelle
foreach (int i in myIntCollection.ToArray()) { myIntCollection.Remove(42); }
für jede Aufzählung neu geschrieben werden undList<T>
unterstützt diese Methode speziell auch in .NET 2.0.Wenn Sie an hoher Leistung interessiert sind, können Sie zwei Listen verwenden. Das Folgende minimiert die Speicherbereinigung, maximiert die Speicherlokalität und entfernt niemals ein Element aus einer Liste. Dies ist sehr ineffizient, wenn es nicht das letzte Element ist.
quelle
Für diejenigen, denen es helfen kann, habe ich diese Erweiterungsmethode geschrieben, um Elemente zu entfernen, die mit dem Prädikat übereinstimmen, und die Liste der entfernten Elemente zurückzugeben.
quelle