Странная "коллекция была изменена после создания экземпляра перечислителя" исключение

возможно, кто-то может указать мне в правильном направлении, потому что я полностью в тупике.

у меня есть функция, которая просто распечатывает LinkedList классов:

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

на Component "объект" на самом деле имеет обычай ToString() вызовы так:

    int Id;
    ...
    public override String ToString()
    {
        return GetType() + ": " + Id;
    }

эта функция обычно работает нормально-однако я столкнулся с проблемой, что когда она строит около 30 или около того записей в списке,PrintcomplentList foreach заявление возвращается с InvalidOperationException: Collection was modified after the enumerator was instantiated.

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

я полностью в тупике, кто - нибудь еще там столкнулся с этим?

4 ответов


Я подозреваю, что место для начала поиска будет в любых местах, где вы манипулируете списком, т. е. вставляете/удаляете/повторно назначаете элементы. Мое подозрение в том, что где-то будет обратный вызов/даже-обработчик, который запускается асинхронно (возможно, как часть XNA краски etc loops), и который редактирует список - по существу, вызывая эту проблему как условие гонки.

чтобы проверить, так ли это, поместите некоторые выходные данные отладки/трассировки вокруг мест, которые манипулируйте списком и посмотрите, запускает ли он когда-либо (и, в частности, непосредственно перед исключением) код манипуляции одновременно с выводом консоли:

private void SomeCallback()
{
   Console.WriteLine("---Adding foo"); // temp investigation code; remove
   components.AddLast(foo);
   Console.WriteLine("---Added foo"); // temp investigation code; remove
}

к сожалению, такие вещи часто являются болью для отладки, так как изменение кода для его исследования часто меняет проблему (a плавающая ошибка).

одним из ответов было бы синхронизировать доступ; т. е. в все места, которые редактируют список, используйте lock полный операция:

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
}

и в вашем обратном вызове (или что-то еще)

private void SomeCallback()
{
   lock(syncLock)
   {
       components.AddLast(foo);
   }
}

в частности, "полная операция" может включать:

  • проверьте счетчик и foreach/for
  • проверять наличие и вставить/удалить
  • etc

(т. е. не отдельные/дискретные операции, а единицы работы)


вместо foreach, Я использую while( collection.count >0) затем использовать collection[i].


Я не знаю, имеет ли это отношение к OP, но у меня была та же ошибка и я нашел этот поток во время поиска google. Я смог решить его, добавив перерыв после удаления элемента в цикле.

foreach( Weapon activeWeapon in activeWeapons ){

            if (activeWeapon.position.Z < activeWeapon.range)
            {
                activeWeapons.Remove(activeWeapon);
                break; // Fixes error
            }
            else
            {
                activeWeapon.position += activeWeapon.velocity;
            }
        }
    }

Если вы опустите разрыв, вы получите ошибку " InvalidOperationException: коллекция была изменена после создания экземпляра перечислителя."


использование Break может быть способом, но это может повлиять на вашу серию операций. Что я делаю в этом случае, просто преобразую foreach в традиционный for loop

for(i=0;i<List.count;i++)
{
List.Remove();
i--;
}

это работает без каких-либо проблем.