2011-06-04 1 views
7

Je n'ai pas utilisé Queues<T> à un degré réel avant, donc je pourrais manquer quelque chose évidente. Je suis en train de parcourir un Queue<EnemyUserControl> comme celui-ci (toutes les images):boucle Queue ForEach lancer InvalidOperationException

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

Quand un ennemi meurt, le contrôle utilisateur ennemi déclenche un événement que je suis abonné et je le faire (le premier ennemi dans la file d'attente est supprimé par la conception):

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(); 
} 

Cependant, après la méthode Dequeue est appelée, je reçois un InvalidOperationException sur la boucle foreach. Lorsque j'utilise Peek à la place, il n'y a pas d'erreurs donc il doit faire quelque chose avec le changement de la file d'attente elle-même puisque Dequeue supprime l'objet. Ma première supposition est que c'est se plaindre que je modifie une collection qui est itérée par l'énumérateur, mais la dequeuing est effectuée en dehors de la boucle?

Toutes les idées ce qui pourrait être la cause de ce problème?

Merci

+1

Vous devez utiliser 'while (queue.Any()) queue.Dequeue();' – Telemat

Répondre

16

vous modifiez la file d'attente à l'intérieur de la boucle foreach. C'est ce qui provoque l'exception.
Code simplifié pour démontrer la question:

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

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

solution possible est d'ajouter ToList(), comme ceci:

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

D 'oh, peu d'un moment de facepalm. Une des méthodes dans le code AI appelle une méthode 'Movement' qui, à son tour, soulève l'événement tué (je pensais qu'il a été soulevé par du code en dehors de la boucle), donc la dequeue est effectuée dans la boucle. La méthode 'ToList()' fonctionne parfaitement. Merci! – keyboardP

1

Ce comportement est typique des agents recenseurs. La plupart des énumérateurs sont conçus pour fonctionner correctement uniquement si la collection sous-jacente reste statique. Si la collection est modifiée pendant une collection puis énumérant le prochain appel à MoveNext, qui est injecté pour vous par le bloc foreach, va générer cette exception.

L'opération Dequeue change évidemment la collection et qui est la cause du problème. La solution consiste à ajouter chaque élément que vous voulez supprimer de la collection cible dans une deuxième collection. Une fois la boucle terminée, vous pouvez ensuite parcourir la deuxième collection et la retirer de la cible.

Cependant, cela pourrait être un peu maladroit, à tout le moins, puisque l'opération Dequeue supprime uniquement l'élément suivant. Vous devrez peut-être passer à un type de collection différent qui autorise des suppressions arbitraires.

Si vous voulez coller avec un Queue, vous serez obligé de retirer chaque élément et de remettre sous condition les éléments qui ne doivent pas être supprimés. Vous aurez toujours besoin de la deuxième collection pour garder une trace des éléments qui peuvent être omis de la mise en file d'attente.

0

Vous ne pouvez pas supprimer des éléments d'une collection tout en réitérant sur eux.

La meilleure solution que j'ai trouvé est d'utiliser un "Liste <> à Supprimer" et ajouter tout ce que vous voulez supprimer à cette liste.Une fois la fin de la boucle foreach, vous pouvez supprimer les éléments de la collection cible en utilisant les références dans la liste des toDelete comme ceci:

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

Maintenant, puisque c'est une file d'attente, vous pourriez simplement être en mesure de compter le nombre de fois vous voulez Dequeue dans un entier et utiliser une simple boucle pour les exécuter plus tard (je n'ai pas beaucoup d'expérience avec les files d'attente à cet égard).

+0

Vous pouvez simplement parcourir la file d'attente et l'effacer. Dans ce cas, l'effet est le même que l'utilisation d'une liste, il n'y a donc aucun intérêt à utiliser une file d'attente pour cela (si vous n'avez pas besoin de la déquiler individuellement). – arni

0

Peu importe où vous modifiez la collection. Si une collection est modifiée pendant l'énumération de ses membres, vous obtenez une exception. Vous pouvez utiliser des verrous et vous assurer que la collection n'est pas modifiée lors de l'itération ou si vous utilisez .NET 4.0, remplacez Queue par ConcurrentQueue.

15

Je sais que c'est un ancien poste mais qu'en ce qui suit:

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

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

Vive

+4

Je vous recommande de changer cela légèrement en un peu/do au lieu de do/while, de sorte que vous effectuez la vérification .Count avant d'essayer le premier .Dequeue(), dans le cas où la file d'attente est vide. – DaveD