Выдача цикла ForEach в очередь InvalidOperationException - PullRequest
11 голосов
/ 04 июня 2011

Раньше я не использовал 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 удаляет объект.Мое первоначальное предположение состоит в том, что я жалуюсь на то, что я изменяю коллекцию, которая перебирается Enumerator, но снятие очереди выполняется вне цикла?

Есть идеи, что может быть причиной этой проблемы?

Спасибо

Ответы [ 6 ]

21 голосов
/ 25 ноября 2013

Я знаю, что это старый пост, но как насчет следующего:

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

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

Приветствия

17 голосов
/ 04 июня 2011

Вы изменяете очередь внутри цикла 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();
}
2 голосов
/ 10 июля 2018

Старый пост, но я подумал, что смогу дать лучший ответ:

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

while (queue.Any())
{
  var val = queue.Dequeue();
}

Поскольку ответ DarkUrse может вызвать исключение, если очередь пуста

1 голос
/ 04 июня 2011

Это типичное поведение счетчиков.Большинство перечислителей предназначены для правильной работы только в том случае, если базовая коллекция остается статической.Если коллекция изменяется при перечислении коллекции, то следующий вызов MoveNext, который вводится для вас блоком foreach, сгенерирует это исключение.

Операция Dequeue, очевидно, изменяет коллекциюи это то, что вызывает проблему.Обходной путь - добавить каждый элемент, который вы хотите удалить из целевой коллекции, во вторую коллекцию.После завершения цикла вы можете перебрать второй набор и удалить его из цели.

Однако, это может быть немного неудобно, по крайней мере, так как операция Dequeue удаляет только следующий элемент,Возможно, вам придется переключиться на другой тип коллекции, который допускает произвольное удаление.

Если вы хотите придерживаться Queue, то вам придется вывести из очереди каждый элемент и условно поставить в очередь те элементы, которые не должныудалить.Вам все еще понадобится вторая коллекция, чтобы отслеживать предметы, которые можно пропустить в очереди.

0 голосов
/ 04 июня 2011

Неважно, где вы изменяете коллекцию. Если коллекция изменена во время перечисления ее членов, вы получите исключение. Вы можете использовать блокировки и удостовериться, что коллекция не изменена при итерации или если вы используете .NET 4.0, замените Queue на ConcurrentQueue.

0 голосов
/ 04 июня 2011

Вы не можете удалять элементы из коллекции, перебирая их.

Лучшее решение, которое я нашел, - это использовать «List <> toDelete» и добавлять все, что вы хотите удалить в этот список.,Когда цикл foreach завершится, вы можете удалить элементы из целевой коллекции, используя ссылки в списке toDelete, например:

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

Теперь, поскольку это очередь, вы можете просто подсчитать числораз вы хотите удалить из очереди целое число и использовать простой цикл for для их последующего выполнения (у меня нет такого большого опыта работы с очередями в этом отношении).

...