web-dev-qa-db-ja.com

InvalidOperationExceptionをスローするForEachループのキュー

私はこれまでQueues<T>を実際に使用したことがないので、明らかな何かが欠けている可能性があります。私は次のようにQueue<EnemyUserControl>を反復しようとしています(すべてのフレーム):

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

敵が死ぬと、敵のユーザーコントロールは、私がサブスクライブしたイベントを発生させ、これを実行します(キュー内の最初の敵は設計により削除されます)。

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

ただし、Dequeueメソッドが呼び出された後、InvalidOperationExceptionループでforeachを取得します。代わりにPeekを使用すると、エラーが発生しないため、Dequeueがオブジェクトを削除するため、キュー自体の変更に何らかの影響があります。私の最初の推測では、列挙子によって繰り返されているコレクションを変更していると文句を言っていますが、デキューはループの外で実行されていますか?

この問題を引き起こしている可能性のあるアイデアはありますか?

ありがとう

14
keyboardP

foreachループ内のキューを変更しています。これが例外の原因です。
問題を示すための簡略化されたコード:

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

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

考えられる解決策は、次のようにToList()を追加することです。

_foreach (var i in queue.ToList())
{
    queue.Dequeue();
}
_
18
Alex Aza

私はこれが古い投稿であることを知っていますが、次はどうですか?

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

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

乾杯

24
DarkUrse

古い投稿ですが、もっと良い答えを提供すると思いました:

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


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

DarkUrseの元の回答はdo/whileを使用しており、空のキューでデキューしようとしたときにキューが空の場合は例外が発生するため、nullキューに対する保護も追加されました

4
JohnChris

これは、列挙子の典型的な動作です。ほとんどの列挙子は、基になるコレクションが静的なままである場合にのみ正しく機能するように設計されています。コレクションの列挙中にコレクションが変更された場合、MoveNextブロックによって挿入されるforeachへの次の呼び出しにより、この例外が生成されます。

Dequeue操作は明らかにコレクションを変更し、それが問題の原因です。回避策は、ターゲットコレクションから削除する各アイテムを2番目のコレクションに追加することです。ループが完了したら、2番目のコレクションを循環して、ターゲットから削除できます。

ただし、Dequeue操作では次の項目しか削除されないため、少なくともこれは少し厄介かもしれません。任意の削除を許可する別のコレクションタイプに切り替える必要がある場合があります。

Queueを使い続けたい場合は、各アイテムをデキューし、削除してはならないアイテムを条件付きで再キューイングする必要があります。再キューイングから除外しても問題がないアイテムを追跡するには、2番目のコレクションが必要です。

1
Brian Gideon

要素を繰り返し処理している間は、コレクションから要素を削除することはできません。

私が見つけた最善の解決策は、「List <> toDelete」を使用して、削除したいものをそのリストに追加することです。 foreachループが終了したら、次のようにtoDeleteリストの参照を使用してターゲットコレクションから要素を削除できます。

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

これはキューであるため、デキューする回数を整数で数え、単純なforループを使用して後で実行できる場合があります(この点に関しては、キューの経験はあまりありません)。

0
June Rhodes

コレクションをどこで変更するかは関係ありません。メンバーを列挙しているときにコレクションが変更されると、例外が発生します。ロックを使用して、反復するときにコレクションが変更されないようにするか、.NET 4.0を使用している場合は、QueueConcurrentQueue に置き換えます。

0
Xaqron