90 votes

Pourquoi List<T>.ForEach permet-elle de modifier sa liste ?

Si j'utilise :

var strings = new List<string> { "sample" };
foreach (string s in strings)
{
  Console.WriteLine(s);
  strings.Add(s + "!");
}

le site Add dans le foreach lance une InvalidOperationException (Collection was modified ; enumeration operation may not execute), ce que je considère comme logique, puisque nous sommes en train de couper l'herbe sous le pied.

Cependant, si j'utilise :

var strings = new List<string> { "sample" };
strings.ForEach(s =>
  {
    Console.WriteLine(s);
    strings.Add(s + "!");
  });

il se tire rapidement une balle dans le pied en bouclant jusqu'à ce qu'il lève une exception OutOfMemoryException.

C'est une surprise pour moi, car j'ai toujours pensé que List.ForEach n'était qu'une enveloppe pour foreach ou pour for .
Quelqu'un a-t-il une explication sur le comment et le pourquoi de ce comportement ?

(Inspiré par Boucle ForEach pour une liste générique répétée à l'infini )

68voto

Thomas Levesque Points 141081

C'est parce que le ForEach n'utilise pas l'énumérateur, elle boucle à travers les éléments avec une méthode for boucle :

public void ForEach(Action<T> action)
{
    if (action == null)
    {
        ThrowHelper.ThrowArgumentNullException(ExceptionArgument.match);
    }
    for (int i = 0; i < this._size; i++)
    {
        action(this._items[i]);
    }
}

(code obtenu avec JustDecompile)

Puisque l'énumérateur n'est pas utilisé, il ne vérifie jamais si la liste a été modifiée, et la condition de fin de la fonction for n'est jamais atteinte car _size est augmenté à chaque itération.

14voto

Alexey Raga Points 3326

List<T>.ForEach est mis en œuvre par for à l'intérieur, donc il n'utilise pas d'énumérateur et il permet de modifier la collection.

6voto

Michael Perrenoud Points 37869

Parce que le ForEach attaché à la classe List utilise en interne une boucle for qui est directement attachée à ses membres internes -- ce que vous pouvez voir en téléchargeant le code source pour le cadre .NET.

http://referencesource.microsoft.com/netframework.aspx

Alors qu'une boucle foreach est avant tout une optimisation du compilateur, mais doit également opérer sur la collection en tant qu'observateur - donc si la collection est modifiée, elle lève une exception.

4voto

David Kean Points 3353

Nous sommes au courant de ce problème, c'était un oubli quand il a été écrit à l'origine. Malheureusement, nous ne pouvons pas le modifier car cela empêcherait maintenant l'exécution de ce code qui fonctionnait auparavant :

        var list = new List<string>();
        list.Add("Foo");
        list.Add("Bar");

        list.ForEach((item) => 
        { 
            if(item=="Foo") 
                list.Remove(item); 
        });

L'utilité de cette méthode en elle-même est discutable car Eric Lippert a fait remarquer, nous ne l'avons donc pas inclus pour .NET pour les applications de style Metro (c'est-à-dire les applications Windows 8).

David Kean (équipe BCL)

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X