Strange «La collection a été modifiée après l'instanciation de l'énumérateur»

StackOverflow https://stackoverflow.com/questions/844850

  •  20-08-2019
  •  | 
  •  

Question

Peut-être que quelqu'un peut me diriger dans la bonne direction, parce que je suis complètement perplexe à ce sujet.

J'ai une fonction qui imprime simplement une liste liée de classes:

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

La Component L'objet a en fait une coutume ToString() Appelez comme tel:

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

Cette fonction fonctionne généralement bien - mais j'ai rencontré le problème que lorsqu'il s'accumule à environ 30 entrées dans la liste, la PrintcomplentList foreach La déclaration revient avec un InvalidOperationException: Collection was modified after the enumerator was instantiated.

Maintenant, comme vous pouvez le voir, je ne modifie pas le code dans la boucle FOR, et je n'ai pas explicitement créé de threads, bien que ce soit dans un environnement XNA (si c'est important). Il convient de noter que l'impression est suffisamment fréquente pour que la sortie de la console ralentit le programme dans son ensemble.

Je suis complètement perplexe, quelqu'un d'autre a-t-il rencontré cela?

Était-ce utile?

La solution

Je soupçonne que l'endroit pour commencer à chercher se trouvera dans tous les endroits où vous manipulez la liste - c'est-à-dire insérer / supprimer / réaffirmer les éléments. Ma suspicion est qu'il y aura un rappel / un mancheur uniforme quelque part qui se fait licencier de manière asynchrone (peut-être dans le cadre du XNA Peinture ETC LOOCS), et qui modifie la liste - provoquant essentiellement ce problème en tant que condition de course.

Pour vérifier si c'est le cas, mettez une sortie de débogage / trace autour des endroits qui manipulent la liste et voyez si jamais (et en particulier, juste avant l'exception) exécute le code de manipulation en même temps que la sortie de votre console:

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

Malheureusement, de telles choses sont souvent une douleur à déboguer, car le changement du code pour l'étudier change souvent le problème (un Heisenbug).

Une réponse serait de synchroniser l'accès; c'est-à-dire dans tout Les endroits qui modifient la liste, utilisez un lock Autour de l'opération complète:

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
}

Et dans votre rappel (ou autre)

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

En particulier, une "opération complète" pourrait inclure:

  • Vérifiez le compte et foreach/for
  • Vérifier l'existence et insérer / supprimer
  • etc

(c'est-à-dire pas les opérations individuelles / discrètes - mais des unités de travail)

Autres conseils

À la place de foreach, J'utilise while( collection.count >0) puis utiliser collection[i].

Je ne sais pas si cela est pertinent pour l'OP, mais j'ai eu la même erreur et j'ai trouvé ce fil lors d'une recherche Google. J'ai pu le résoudre en ajoutant une pause après avoir retiré un élément dans la boucle.

foreach( Weapon activeWeapon in activeWeapons ){

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

Si vous omettez la pause, vous obtiendrez l'erreur "InvalidOperationException: la collection a été modifiée après l'instanciation de l'énumérateur".

Utilisant Break Cela pourrait être un moyen, mais cela peut avoir un impact sur votre série d'exploitation. Ce que je fais dans ce cas pour convertir simplement le foreach à traditionnel for boucle

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

Cela fonctionne sans aucun problème.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top