Question

Une nouvelle fonctionnalité C # / .NET 4.0 est que vous pouvez changer votre dénombrable dans un foreach sans faire l'exception. Voir l'entrée de blog de Paul Jackson Un effet secondaire intéressant de Concurrency: Suppression d'éléments d'une collection énumération Alors que pour plus d'informations sur ce changement

.

Quelle est la meilleure façon de faire ce qui suit?

foreach(var item in Enumerable)
{
    foreach(var item2 in item.Enumerable)
    {
        item.Add(new item2)
    }
}

En général, j'utilise un IList comme un cache / tampon jusqu'à la fin de la foreach, mais est-il mieux?

Était-ce utile?

La solution

La collection utilisée dans foreach est immuable. Ceci est très bien par la conception.

Comme il est dit sur MSDN :

  

L'instruction foreach est utilisé pour   itérer la collection pour obtenir   les informations que vous voulez, mais peut   pas être utilisé pour ajouter ou supprimer des éléments   de la collection source pour éviter   effets secondaires imprévisibles. Si vous   besoin d'ajouter ou supprimer des éléments de la   collection de source, utiliser une boucle.

Le poste dans le fourni par Poko indique que cela est permis dans les nouvelles collections concurrentes.

Autres conseils

Faites une copie de l'énumération, en utilisant une méthode d'extension IEnumerable dans ce cas, et d'énumérer dessus. Cela ajouterait une copie de chaque élément dans chaque dénombrable intérieur de cette énumération.

foreach(var item in Enumerable)
{
    foreach(var item2 in item.Enumerable.ToList())
    {
        item.Add(item2)
    }
}

Comme mentionné précédemment, mais avec un exemple de code:

foreach(var item in collection.ToArray())
    collection.Add(new Item...);

Pour illustrer la réponse de Nippysaurus: Si vous allez Ajouter les nouveaux éléments à la liste et que vous souhaitez traiter les éléments nouvellement ajoutés aussi au cours de la même énumération, vous pouvez simplement utiliser boucle au lieu de foreach boucle, problème résolu:)

var list = new List<YourData>();
... populate the list ...

//foreach (var entryToProcess in list)
for (int i = 0; i < list.Count; i++)
{
    var entryToProcess = list[i];

    var resultOfProcessing = DoStuffToEntry(entryToProcess);

    if (... condition ...)
        list.Add(new YourData(...));
}

Par exemple runnable:

void Main()
{
    var list = new List<int>();
    for (int i = 0; i < 10; i++)
        list.Add(i);

    //foreach (var entry in list)
    for (int i = 0; i < list.Count; i++)
    {
        var entry = list[i];
        if (entry % 2 == 0)
            list.Add(entry + 1);

        Console.Write(entry + ", ");
    }

    Console.Write(list);
}

Sortie de dernier exemple:

0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 3, 5, 7, 9,

Liste (15 articles)
0
1
2
3
4
5
6
7
8
9
1
3
5
7
9

Voici comment vous pouvez le faire (solution rapide et sale Si vous vraiment ont besoin de ce genre de comportement, vous devez soit reconsidérer votre conception ou remplacer tous les membres de IList<T> et regrouper la liste des sources.):

using System;
using System.Collections.Generic;

namespace ConsoleApplication3
{
    public class ModifiableList<T> : List<T>
    {
        private readonly IList<T> pendingAdditions = new List<T>();
        private int activeEnumerators = 0;

        public ModifiableList(IEnumerable<T> collection) : base(collection)
        {
        }

        public ModifiableList()
        {
        }

        public new void Add(T t)
        {
            if(activeEnumerators == 0)
                base.Add(t);
            else
                pendingAdditions.Add(t);
        }

        public new IEnumerator<T> GetEnumerator()
        {
            ++activeEnumerators;

            foreach(T t in ((IList<T>)this))
                yield return t;

            --activeEnumerators;

            AddRange(pendingAdditions);
            pendingAdditions.Clear();
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            ModifiableList<int> ints = new ModifiableList<int>(new int[] { 2, 4, 6, 8 });

            foreach(int i in ints)
                ints.Add(i * 2);

            foreach(int i in ints)
                Console.WriteLine(i * 2);
        }
    }
}

LINQ est très efficace pour jongler avec des collections.

Vos types et la structure ne sont pas clairs pour moi, mais je vais essayer d'adapter votre exemple au mieux de ma capacité.

A partir de votre code, il semble que, pour chaque élément, vous ajoutez que tout élément de sa propre propriété 'Enumerable. Ceci est très simple:

foreach (var item in Enumerable)
{
    item = item.AddRange(item.Enumerable));
}

A titre d'exemple plus général, disons que nous voulons itérer une collection et supprimer des éléments où une certaine condition est vraie. Éviter foreach, en utilisant LINQ:

myCollection = myCollection.Where(item => item.ShouldBeKept);

Ajouter un élément en fonction de chaque élément existant? Pas de problème:

myCollection = myCollection.Concat(myCollection.Select(item => new Item(item.SomeProp)));

Vous ne pouvez pas modifier la collection dénombrable alors qu'elle est recensée, vous devrez faire vos modifications avant ou après avoir énuméré.

La boucle for est une bonne alternative, mais si votre collection de IEnumerable ne met pas en œuvre ICollection, il est impossible.

Soit:

1) Collection copie. Énumérer la collection et changer la collection originale copié lors de l'énumération. (@Tvanfosson)

ou

2) Gardez une liste des modifications et de les engager après l'énumération.

La meilleure approche du point de vue de la performance est probablement d'utiliser un ou deux tableaux. Copiez la liste à un tableau, faire des opérations sur le tableau, puis construire une nouvelle liste à partir du tableau. L'accès à un élément de tableau est plus rapide que l'accès à un élément de la liste, et les conversions entre un List<T> et un T[] peut utiliser une opération de « copie en bloc » rapide qui évite les frais généraux associés accéder à des objets.

Par exemple, supposons que vous avez un List<string> et souhaitez avoir toutes les chaînes dans la liste qui commence par T être suivi par un élément « Boo », alors que chaque chaîne qui commence par « U » est entièrement abandonnée. Une approche optimale serait probablement quelque chose comme:

int srcPtr,destPtr;
string[] arr;

srcPtr = theList.Count;
arr = new string[srcPtr*2];
theList.CopyTo(arr, theList.Count); // Copy into second half of the array
destPtr = 0;
for (; srcPtr < arr.Length; srcPtr++)
{
  string st = arr[srcPtr];
  char ch = (st ?? "!")[0]; // Get first character of string, or "!" if empty
  if (ch != 'U')
    arr[destPtr++] = st;
  if (ch == 'T')
    arr[destPtr++] = "Boo";
}
if (destPtr > arr.Length/2) // More than half of dest. array is used
{
  theList = new List<String>(arr); // Adds extra elements
  if (destPtr != arr.Length)
    theList.RemoveRange(destPtr, arr.Length-destPtr); // Chop to proper length
}
else
{
  Array.Resize(ref arr, destPtr);
  theList = new List<String>(arr); // Adds extra elements
}

Il aurait été utile si List<T> a fourni une méthode pour construire une liste d'une partie d'un tableau, mais je ne suis pas au courant d'une méthode efficace pour le faire. Cependant, les opérations sur les tableaux sont assez rapides. Il faut souligner le fait que l'ajout et la suppression d'éléments de la liste ne nécessite pas « pousser » autour d'autres objets; chaque élément s'écrit directement à son endroit approprié dans le tableau.

Vous devriez vraiment utiliser for() au lieu de foreach() dans ce cas.

Pour ajouter au LINQ réponse de Timo peut être utilisé comme celui-ci ainsi:

items = items.Select(i => {

     ...
     //perform some logic adding / updating.

     return i / return new Item();
     ...

     //To remove an item simply have logic to return null.

     //Then attach the Where to filter out nulls

     return null;
     ...


}).Where(i => i != null);

Je l'ai écrit un pas facile, mais en raison de cette performance sera dégradée

Voici mon extrait de code: -

for (int tempReg = 0; tempReg < reg.Matches(lines).Count; tempReg++)
                            {
                                foreach (Match match in reg.Matches(lines))
                                {
                                    var aStringBuilder = new StringBuilder(lines);
                                    aStringBuilder.Insert(startIndex, match.ToString().Replace(",", " ");
                                    lines[k] = aStringBuilder.ToString();
                                    tempReg = 0;
                                    break;
                                }
                            }
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top