it-swarm.com.de

Intelligente Methode zum Entfernen von Elementen aus einer Liste <T> während der Aufzählung in C #

Ich habe den klassischen Fall des Versuchs, 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 eine InvalidOperationException ausgelöst, da Enumeratoren es nicht mögen, wenn sich die zugrunde liegende Auflistung ä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 davon scheint eine gute Lösung zu haben:

  1. Löschen Sie nicht innerhalb dieser Schleife, sondern führen Sie stattdessen eine separate "Löschliste" aus, die Sie nach der Hauptschleife bearbeiten. 

    Dies ist normalerweise eine gute Lösung, aber in meinem Fall brauche ich den Artikel sofort als "Warten" bis danach. Die Hauptschleife, die den Artikel wirklich löscht, ändert den logischen Fluss meines Codes.

  2. Anstatt den Artikel zu löschen, setzen Sie einfach ein Flag auf den Artikel und markieren ihn als inaktiv. Fügen Sie dann die Funktionalität von Muster 1 hinzu, um die Liste zu bereinigen. 

    Dieses würde funktioniert für alle meine Anforderungen, aber es bedeutet, dass ein lot Code geändert werden muss, um das inaktive Flag bei jedem Zugriff auf ein Element zu überprüfen. Dies ist viel zu viel Administration für meinen Geschmack.

  3. Binden Sie die Ideen von Muster 2 irgendwie in eine Klasse ein, die von List<T> abgeleitet ist. Diese Superliste behandelt das inaktive Flag und das Löschen von Objekten im Anschluss an die Tatsache. Außerdem werden Elemente, die als inaktiv markiert sind, nicht für Enumerationskonsumenten verfügbar gemacht. Im Grunde kapselt es einfach alle Ideen von Muster 2 (und anschließend Muster 1). 

    Gibt es eine solche Klasse? Hat jemand Code dafür? Oder gibt es einen besseren Weg?

  4. Mir wurde gesagt, dass der Zugriff auf myIntCollection.ToArray() anstelle von myIntCollection das Problem lösen und mir erlauben wird, innerhalb der Schleife zu löschen. 

    Dies scheint mir ein schlechtes Designmuster zu sein, oder ist es in Ordnung?

Einzelheiten:

  • Die Liste enthält viele Elemente und ich werde nur einige davon entfernen.

  • Innerhalb der Schleife werde ich alle möglichen Prozesse ausführen, hinzufügen, entfernen usw., also muss die Lösung ziemlich allgemein sein.

  • Das Element, das ich löschen muss, darf nicht ist das aktuelle Element in der Schleife. Zum Beispiel kann ich auf Position 10 einer 30-Element-Schleife sein und muss Artikel 6 oder Artikel 26 entfernen. Das Rückwärtsgehen durch das Array wird aus diesem Grund nicht mehr funktionieren. ;O(

78
John Stock

Die beste Lösung ist in der Regel die Methode RemoveAll() :

myList.RemoveAll(x => x.SomeProp == "SomeValue");

Oder wenn Sie bestimmte Elemente entfernen müssen:

MyListType[] elems = new[] { elem1, elem2 };
myList.RemoveAll(x => elems.Contains(x));

Dies setzt voraus, dass Ihre Schleife natürlich nur zum Entfernen bestimmt ist. Wenn Sie do eine zusätzliche Verarbeitung benötigen, ist die beste Methode in der Regel die Verwendung einer for- oder while-Schleife, da Sie keinen Enumerator verwenden:

for (int i = myList.Count - 1; i >= 0; i--)
{
    // Do processing here, then...
    if (shouldRemoveCondition)
    {
        myList.RemoveAt(i);
    }
}

Beim Rückwärtsgehen wird sichergestellt, dass Sie keine Elemente überspringen.

Antwort auf Edit:

Wenn Sie scheinbar willkürliche Elemente entfernen möchten, besteht die einfachste Methode darin, einfach die Elemente zu verfolgen, die Sie entfernen möchten, und anschließend alle Elemente gleichzeitig zu entfernen. Etwas wie das:

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));
182
dlev

Wenn Sie einen List<T> auflisten und daraus entfernen müssen, schlage ich vor, eine while-Schleife anstelle einer foreach

var index = 0;
while (index < myList.Count) {
  if (someCondition(myList[index])) {
    myList.RemoveAt(index);
  } else {
    index++;
  }
}
20
JaredPar

Wenn Sie eine Liste durchlaufen müssen und sie möglicherweise während der Schleife ändern, sollten Sie eine for-Schleife verwenden:

for (int i = 0; i < myIntCollection.Count; i++)
{
    if (myIntCollection[i] == 42)
    {
        myIntCollection.Remove(i);
        i--;
    }
}

Natürlich müssen Sie vorsichtig sein, zum Beispiel dekrementiere ich i, wenn ein Element entfernt wird, da sonst Einträge übersprungen werden (alternativ können Sie die Liste rückwärts durchgehen).

Wenn Sie Linq haben, sollten Sie einfach RemoveAll verwenden, wie dlev vorgeschlagen hat.

8
Justin

Ich weiß, dass dieser Beitrag alt ist, aber ich dachte, ich würde teilen, was für mich funktioniert hat. 

Erstellen Sie eine Kopie der Liste zum Aufzählen, und in der for-Schleife können Sie die kopierten Werte bearbeiten und mit der Quellliste/add/what entfernen. 

private void ProcessAndRemove(IList<Item> list)
{
    foreach (var item in list.ToList())
    {
        if (item.DeterminingFactor > 10)
        {
            list.Remove(item);
        }
    }
}
7
D-Jones

Fügen Sie bei der Aufzählung der Liste die Liste, die Sie behalten möchten, zu einer neuen Liste hinzu. Weisen Sie anschließend die neue Liste der myIntCollection zu.

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;
4
James Curran

Fügen wir Ihren Code hinzu:

List<int> myIntCollection=new List<int>();
myIntCollection.Add(42);
myIntCollection.Add(12);
myIntCollection.Add(96);
myIntCollection.Add(25);

Wenn Sie die Liste ändern möchten, während Sie sich in einem Foreach befinden, müssen Sie .ToList() eingeben.

foreach(int i in myIntCollection.ToList())
{
    if (i == 42)
       myIntCollection.Remove(96);
    if (i == 25)
       myIntCollection.Remove(42);
}
3

Wie wäre es mit

int[] tmp = new int[myIntCollection.Count ()];
myIntCollection.CopyTo(tmp);
foreach(int i in tmp)
{
    myIntCollection.Remove(42); //The error is no longer here.
}
0
Olaf

Wenn Sie sich für hohe Leistung interessieren, können Sie zwei Listen verwenden. Im Folgenden wird die Speicherbereinigung minimiert, die Speicherlokalität maximiert und ein Element nie wirklich aus einer Liste entfernt. Dies ist sehr ineffizient, wenn es nicht das letzte Element ist.

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;
}
0
Will Calderwood