Du bist nicht angemeldet.

Stilllegung des Forums
Das Forum wurde am 05.06.2023 nach über 20 Jahren stillgelegt (weitere Informationen und ein kleiner Rückblick).
Registrierungen, Anmeldungen und Postings sind nicht mehr möglich. Öffentliche Inhalte sind weiterhin zugänglich.
Das Team von spieleprogrammierer.de bedankt sich bei der Community für die vielen schönen Jahre.
Wenn du eine deutschsprachige Spieleentwickler-Community suchst, schau doch mal im Discord und auf ZFX vorbei!

Werbeanzeige

Cross

Frischling

  • »Cross« ist der Autor dieses Themas

Beiträge: 11

Wohnort: Heidelberg

Beruf: Fachinformatiker (Anwendungsentwicklung)

  • Private Nachricht senden

1

31.03.2016, 22:01

[Unity3D / C#] Problem beim Durchloopen einer Liste

Guten Tag,

ich habe folgendes Problem:

Ich habe eine Liste mit Kategorien und möchte dort Elemente löschen.
Dies mache ich über ein Unity-Editor-Window.

Dort liste ich alle Kategorien auf und lasse hinter den Labels eine Button zum entfernen zeichnen.

Wenn ich nun den jeweiligen Eintrag auf aus Liste löschen möchte, bekomme ich folgende Exception:

Quellcode

1
InvalidOperationException: Collection was modified; enumeration operation may not execute.


Mit foreach ist das anscheinend nicht möglich.
Ich habe mir deshalb eine neue Liste names 'categoriesToRemove' erstellt, mit der ich dann alle Einträge aus der Hauptliste lösche.

Hier ist mein Code:

C#-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public void RemoveCategory(string name)
{
    foreach (var category in ItemDatabase.Categories)
    {
        if (category.Equals(name))
        {
            categoriesToRemove.Add(category);
        }
    }
    foreach (var category in categoriesToRemove.ToList())
    {
        ItemDatabase.Categories.Remove(category);
    }
    categoriesToRemove.Clear();
}



Hat jemand eine Idee woran das liegen könnte?
Habe das auch schon mit einer for()-Schleife versucht... brachte auch nichts.

Grüße

Schorsch

Supermoderator

Beiträge: 5 145

Wohnort: Wickede

Beruf: Softwareentwickler

  • Private Nachricht senden

2

31.03.2016, 22:45

Das Problem ist eigentlich relativ simpel. Stell dir das ganze mal als for-Schleife vor:

Quellcode

1
2
3
4
5
6
7
array daten; // hier stecken deine Daten drin
int size; // size hat die länge deines Arrays, also die Anzahl der Daten
for(int i = 0; i < size; i++) {
  if(daten[i].shouldDelete()) {
    daten.remove(i);
  }
}

einfach mal als Pseudocode. Jetzt mal angenommen dein Array hätte die Länge 3. Das Element an Stelle 0 gibt bei shouldDelete true zurück. Das bedeutet das Element an Stelle 0 wird gelöscht. Danach hat dein Array 2 Elemente da das erste gelöscht wurde. Das Element welches vorher an Stelle 1 war ist nun an Stelle 0 und das welches an Stelle 2 war ist nun an Stelle 1. Als nächstes wird i erhöht. i hat jetzt den Wert 1. Das nächste Element wäre jetzt aber an Stelle 0 und nicht an Stelle 1. Das heißt die Reihenfolge wird zerstört auf der du arbeiten möchtest.
foreach arbeitet mit IEnumerable. Dabei hast du einen Enumerator welcher das nächste Element zurück liefern kann. Du kannst dir das grob vorstellen wie dein i welches du am Ende erhöhst. Anstatt das i aber nun von Hand zu erhöhen wird auf dem Iterator eine Funktion aufgerufen die das nächste Element liefert. Wenn du jetzt ein Element löschst kann dieses Element eben nicht mehr das nächste Element zurück geben. Es wurde ja gelöscht.
So zumindest die grobe Theorie dahinter. Was da genau im Hintergrund umgesetzt wird kann ich dir tatsächlich nicht genau sagen. Was du aber machen kannst um dein Problem zu lösen ist dass du das ganze in eine for-Schleife packst und i nur erhöhst wenn du das aktuelle Element nicht löschst. Löschst du ein Element bleibt i gleich. Eben weil das nächste Element jetzt an der selben i-Position im Array sitzt.

Array und List sind hier erst mal gleich zu setzen. Möchtest du etwas löschen, benutz nie foreach sondern for.
„Es ist doch so. Zwei und zwei macht irgendwas, und vier und vier macht irgendwas. Leider nicht dasselbe, dann wär's leicht.
Das ist aber auch schon höhere Mathematik.“

3

31.03.2016, 23:13

Ich denke die Beschreibung von Schorsch zeigt schon warum dieses Verhalten vom Entwickler der Collection gewählt worden sein könnte.

Aber den Code kann man noch übersichtlicher gestalten.


Hier ist mein Code:

C#-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public void RemoveCategory(string name)
{
    foreach (var category in ItemDatabase.Categories)
    {
        if (category.Equals(name))
        {
            categoriesToRemove.Add(category);
        }
    }
    foreach (var category in categoriesToRemove.ToList())
    {
        ItemDatabase.Categories.Remove(category);
    }
    categoriesToRemove.Clear();
}



Das geht einfacher indem du vor dem Enumerieren eine Kopie des vorherigen Zustands anlegst.
So sieht das übersichtlicher aus da du dir die zusätzliche benannte variable sparst:

C#-Quelltext

1
2
3
4
5
6
7
foreach (var item in collection.ToArray())
{
    if (shouldRemove(item))
    {
        collection.Remove(item);
    }
}

Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von »Enguerrand« (31.03.2016, 23:18)


Cross

Frischling

  • »Cross« ist der Autor dieses Themas

Beiträge: 11

Wohnort: Heidelberg

Beruf: Fachinformatiker (Anwendungsentwicklung)

  • Private Nachricht senden

4

31.03.2016, 23:45

@Schorsch:

Danke für die ausführliche Erklärung!
Ich hatte es zuvor auch bereits mit einer for-Schleife versucht, sowie der Vorschlag von @Enguerrand.

Zwar wird der Eintrag gelöscht und die GUI aktualisiert auch die Liste, jedoch schmeißt er mir immer noch die Exception.


// edit:
Anmkerung: Ich rufe die Funktion in der OnGUI-Funktion auf.

Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von »Cross« (01.04.2016, 00:02)


Sacaldur

Community-Fossil

Beiträge: 2 301

Wohnort: Berlin

Beruf: FIAE

  • Private Nachricht senden

5

01.04.2016, 01:03

Enguerrand hat zwar eine relativ hübsche Variante gezeigt, allerdings wird dabei ein neues Array erzeugt.
Besser wäre es, mit Hilfe einer normalen Zählschleife zu iterieren. Um die Laufvariable nicht nach jedem Entfernen anpassen zu müssen, könnte man auch rückwärts über die Liste iterieren (also mit dem größten Index Anfang und in jedem Schritt dekrementieren).

Was genau meinst du damit, dass die Exception immernoch geschmissen wird? Bist du sicher, dass du kein foreach mehr hast?


Und ich hoffe, dass du damit nicht dein Ingame-UI sondern nur Editor-Sachen damit realisierst. Für das UI-Zeug im Spiel bietet Unity schon seit 4.6 oder so ein neues UI-System, welches wesentlich besser geeignet ist.
Spieleentwickler in Berlin? (Thema in diesem Forum)
---
Es ist ja keine Schande etwas falsch zu machen, als Programmierer tu ich das täglich, [...].

BlueCobold

Community-Fossil

Beiträge: 10 738

Beruf: Teamleiter Mobile Applikationen & Senior Software Engineer

  • Private Nachricht senden

6

01.04.2016, 08:05

Es geht übrigens noch viel kürzer:

C#-Quelltext

1
ItemDatabase.Categories.RemoveAll(category => category.Equals(name));

Damit kann man sich sogar die komplette RemoveCategory Methode sparen.
Teamleiter von Rickety Racquet (ehemals das "Foren-Projekt") und von Marble Theory

Willkommen auf SPPRO, auch dir wird man zu Unity oder zur Unreal-Engine raten, ganz bestimmt.[/Sarkasmus]

Sacaldur

Community-Fossil

Beiträge: 2 301

Wohnort: Berlin

Beruf: FIAE

  • Private Nachricht senden

7

01.04.2016, 12:41

Aufgrund von Unity sollte man noch dazu sagen, dass using System.Linq; eingebunden werden muss. In Visual Studio wird dieses using zwar automatisch neuen Dateien ergänzt, für Unity-Scripts (also über den Editor erzeugte Scripte) ist das aber nicht der Fall.
Spieleentwickler in Berlin? (Thema in diesem Forum)
---
Es ist ja keine Schande etwas falsch zu machen, als Programmierer tu ich das täglich, [...].

8

01.04.2016, 15:52

Ansonsten kann man diesem "Fehler" prinzipiell ausweichen, indem man einfach umgekehrt mit ner for-Schleife (performance-technisch sowieso vorzuziehen) durch die Liste iteriert.

Schorsch

Supermoderator

Beiträge: 5 145

Wohnort: Wickede

Beruf: Softwareentwickler

  • Private Nachricht senden

9

01.04.2016, 18:48

Ansonsten kann man diesem "Fehler" prinzipiell ausweichen, indem man einfach umgekehrt mit ner for-Schleife (performance-technisch sowieso vorzuziehen) durch die Liste iteriert.

Es geht schneller rückwärts zu iterieren? Wieso? Habe mal Google gecheckt und da wird auch nichts zufriedenstellendes ausgespuckt. Klar, wenn ich in der Bedingung einer for-Schleife jedes mal die Größe meiner Datenstruktur abfrage und die intern dynamisch berechnet wird, dann dauert das länger als wenn ich mit konstant 0 vergleiche aber dafür kann ich mir die Länge ja auch einfach ein mal holen und speichern. Dann habe ich bei der Bedingung die Variable im vergleich zur Konstanten und das sollte am Ende nicht viel ausmachen.
„Es ist doch so. Zwei und zwei macht irgendwas, und vier und vier macht irgendwas. Leider nicht dasselbe, dann wär's leicht.
Das ist aber auch schon höhere Mathematik.“

Wirago

Alter Hase

Beiträge: 1 193

Wohnort: Stockerau

Beruf: CRM Application Manager

  • Private Nachricht senden

10

01.04.2016, 22:24

Die Länge verändert sich ja bei dem Löschen von Einträgen. Wenn du dir am Anfang die Länge holst und während das Abarbeiten Einträge löscht, greifst du am Ende auf Indizes zu die es nicht mehr gibt. IndexOutOfBounds Exception lässt Grüßen ;)

Werbeanzeige