Strana "Collezione è stata modificata dopo che l'enumeratore è stato istanziato" Eccezione
-
20-08-2019 - |
Domanda
Forse qualcuno può indicarmi nella direzione corretta, perché sono completamente sconcertato su questo.
Ho una funzione che stampela semplicemente una lista di lezioni Linked:
LinkedList<Component> components = new LinkedList<Component>();
...
private void PrintComponentList()
{
Console.WriteLine("---Component List: " + components.Count + " entries---");
foreach (Component c in components)
{
Console.WriteLine(c);
}
Console.WriteLine("------");
}
Il Component
L'oggetto in realtà ha un'usanza ToString()
Chiama come tale:
int Id;
...
public override String ToString()
{
return GetType() + ": " + Id;
}
Questa funzione in genere funziona bene, tuttavia ho riscontrato il problema che quando si basa su circa 30 voci nell'elenco, il PrintcomplentList
foreach
la dichiarazione ritorna con un InvalidOperationException: Collection was modified after the enumerator was instantiated.
Ora, come puoi vedere, non sto modificando il codice all'interno del loop per loop e non ho creato esplicitamente alcun thread, sebbene questo sia all'interno di un ambiente XNA (se conta). Va notato che la stampa è abbastanza frequente che l'output della console sta rallentando il programma nel suo insieme.
Sono completamente perplesso, qualcun altro là fuori ha incontrato questo?
Soluzione
Sospetto che il posto dove iniziare a cercare sarà in tutti i luoghi in cui manipoli l'elenco - cioè inserisci/rimuovi/riassegna gli elementi. Il mio sospetto è che ci sarà un callback/un gestore pari da qualche parte che viene sparato in modo asincrono (forse come parte dell'XNA dipingere ETC LOOP) e che sta modificando l'elenco - causando essenzialmente questo problema come condizione di gara.
Per verificare se questo è il caso, inserisci un output di debug/traccia attorno ai luoghi che manipolano l'elenco e vedi se mai (e in particolare, poco prima dell'eccezione) esegue il codice di manipolazione contemporaneamente all'output della console:
private void SomeCallback()
{
Console.WriteLine("---Adding foo"); // temp investigation code; remove
components.AddLast(foo);
Console.WriteLine("---Added foo"); // temp investigation code; remove
}
Sfortunatamente, tali cose sono spesso un dolore al debug, poiché cambiare il codice per indagare spesso cambia il problema (a Heisenbug).
Una risposta sarebbe quella di sincronizzare l'accesso; cioè in tutto I luoghi che modificano l'elenco, usano un file lock
Intorno all'operazione completa:
LinkedList<Component> components = new LinkedList<Component>();
readonly object syncLock = new object();
...
private void PrintComponentList()
{
lock(syncLock)
{ // take lock before first use (.Count), covering the foreach
Console.WriteLine("---Component List: " + components.Count
+ " entries---");
foreach (Component c in components)
{
Console.WriteLine(c);
}
Console.WriteLine("------");
} // release lock
}
E nel tuo callback (o altro)
private void SomeCallback()
{
lock(syncLock)
{
components.AddLast(foo);
}
}
In particolare, un "funzionamento completo" potrebbe includere:
- Controlla il conteggio e
foreach
/for
- controlla l'esistenza e inserire/rimuovere
- eccetera
(cioè non le operazioni individuali/discrete - ma unità di lavoro)
Altri suggerimenti
Invece di foreach
, Io uso while( collection.count >0)
Quindi usa collection[i]
.
Non so se questo sia rilevante per l'OP, ma ho avuto lo stesso errore e ho trovato questo thread durante una ricerca su Google. Sono stato in grado di risolverlo aggiungendo una pausa dopo aver rimosso un elemento nel ciclo.
foreach( Weapon activeWeapon in activeWeapons ){
if (activeWeapon.position.Z < activeWeapon.range)
{
activeWeapons.Remove(activeWeapon);
break; // Fixes error
}
else
{
activeWeapon.position += activeWeapon.velocity;
}
}
}
Se lasci fuori la pausa, otterrai l'errore "InvalidOperationException: la raccolta è stata modificata dopo l'istanziata dell'enumeratore."
Using Break
could be a way but it may impact your series of operation.
What I do in that case in simply convert the foreach
to traditional for
loop
for(i=0; i < List.count; i++)
{
List.Remove();
i--;
}
This works without any issues.