2011-06-04 19 views
7

No he usado Queues<T> en ningún grado real, por lo que podría estar perdiendo algo obvio. Estoy tratando de repetición de un Queue<EnemyUserControl> como esto (todos los fotogramas):Queue ForEach loop throwing InvalidOperationException

foreach (var e in qEnemy) 
{ 
    //enemy AI code 
} 

Cuando un enemigo muere, el control de usuario enemigo provoca un evento me he suscrito a y yo hacer esto (el primer enemigo en el cola se elimina por diseño):

void Enemy_Killed(object sender, EventArgs e) 
{  
    qEnemy.Dequeue(); 

    //Added TrimExcess to check if the error was caused by NULL values in the Queue (it wasn't :)) 
    qEnemy.TrimExcess(); 
} 

Sin embargo, después de que el método de quitar de la cola se llama, consigo un InvalidOperationException en el bucle foreach. Cuando uso Peek en su lugar, no hay errores, por lo que tiene que hacer algo con el cambio de la cola en sí, ya que Dequeue quita el objeto. Mi conjetura inicial es que se está quejando de que estoy modificando una colección que está siendo iterada por el Enumerator, ¿pero la dequeración se está realizando fuera del ciclo?

¿Alguna idea de lo que podría estar causando este problema?

Gracias

+1

Debe utilizar 'while (queue.Any()) queue.Dequeue();' D – Telemat

Respuesta

16

Está modificando la cola dentro del lazo foreach. Esto es lo que causa la excepción.
código simplificado para demostrar el problema:

var queue = new Queue<int>(); 
queue.Enqueue(1); 
queue.Enqueue(2); 

foreach (var i in queue) 
{ 
    queue.Dequeue(); 
} 

Posible solución es añadir ToList(), así:

foreach (var i in queue.ToList()) 
{ 
    queue.Dequeue(); 
} 
+0

'Oh, un poco de un momento facepalm. Uno de los métodos en el código de IA llama a un método de 'Movimiento' que, a su vez, plantea el evento muerto (pensé que fue levantado por algún código fuera del bucle), por lo que la dequeración se realiza dentro del bucle. El método 'ToList()' funciona perfectamente. ¡Gracias! – keyboardP

1

Comportamiento típico de los enumeradores. La mayoría de los enumeradores están diseñados para funcionar correctamente solo si la colección subyacente permanece estática. Si la colección se cambia al enumerar una colección, la próxima llamada al MoveNext, que se inyecta para usted en el bloque foreach, generará esta excepción.

La operación Dequeue obviamente cambia la colección y eso es lo que está causando el problema. La solución consiste en agregar cada elemento que desee eliminar de la colección de destino a una segunda colección. Después de completar el ciclo, puede recorrer la segunda colección y eliminarla del objetivo.

Sin embargo, esto podría ser un poco incómodo, al menos, ya que la operación Dequeue solo elimina el siguiente elemento. Es posible que deba cambiar a un tipo de colección diferente que permita eliminaciones arbitrarias.

Si desea seguir con un Queue, se verá forzado a quitar la cola de cada elemento y volver a poner en cola condicionalmente los elementos que no se deben eliminar. Aún necesitará la segunda colección para realizar un seguimiento de los elementos que está bien omitir en la re-puesta en cola.

0

No se puede quitar elementos de una colección al iterar sobre ellos.

La mejor solución que he encontrado es usar una "Lista <> para Eliminar" y agregar lo que quiera eliminar a esa lista.Una vez que termina el bucle foreach, puede eliminar los elementos de la colección de destino utilizando las referencias en la lista toDelete así:

foreach (var e in toDelete) 
    target.Remove(e); 
toDelete.Clear(); 

Ahora ya que esta es una cola, se podría simplemente ser capaz de contar el número de veces desea Dequeue en un entero y usar un bucle for para ejecutarlos más tarde (no tengo mucha experiencia con colas en este sentido).

+0

Simplemente puede iterar sobre la cola y borrarla. En ese caso, el efecto es el mismo que usar una Lista, por lo que no tiene sentido utilizar una cola para esto (si no es necesario que dequeue de forma individual). – arni

0

No importa dónde esté modificando la colección. Si se modifica una colección mientras enumera sus miembros, obtiene una excepción. Puede usar bloqueos y asegurarse de que la colección no se modifique al realizar la iteración o si está utilizando .NET 4.0, reemplace Queue con ConcurrentQueue.

15

Sé que esto es una entrada antigua, pero ¿qué pasa con lo siguiente:

var queue = new Queue<int>(); 
queue.Enqueue(1); 
queue.Enqueue(2); 

do { 
    var val = queue.Dequeue(); 
} 
while (queue.Count > 0); 

Saludos

+4

Recomiendo cambiar esto ligeramente por un rato/do en lugar de hacerlo/hacerlo, para que esté realizando la comprobación .Count antes de intentar la primera .Dequeue(), en caso de que la cola esté vacía. – DaveD

Cuestiones relacionadas