Domanda

Una nuova funzione in C# / .NET 4.0 è che puoi cambiare il tuo enumerabile in un foreach senza eccezione.Vedere Paul Jackson blog Un Interessante Effetto collaterale di Concorrenza:La rimozione di Elementi da un insieme Durante l'Enumerazione per informazioni su questa modifica.

Qual è il modo migliore per effettuare le seguenti operazioni?

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

Di solito io uso un IList come cache/buffer fino alla fine del foreach, ma c'è di meglio?

È stato utile?

Soluzione

La collezione usato in foreach è immutabile. Questo è molto più di progettazione.

Come si dice su MSDN :

  

L'istruzione foreach viene utilizzato per   scorrere l'insieme per ottenere   le informazioni che si desidera, ma può   non può essere utilizzato per aggiungere o rimuovere elementi   dalla raccolta di origine per evitare   effetti collaterali imprevedibili. Se   bisogno di aggiungere o rimuovere elementi dal   raccolta fonte, utilizzare un ciclo for.

Il post nel link rel="noreferrer"> href="http://www.lovethedot.net/2009/03/interesting-side-effect-of-concurrency.html" fornito da Poko indica che questo è consentito nelle nuove collezioni contemporanee.

Altri suggerimenti

Fare una copia dell'enumerazione, utilizzando un metodo di estensione IEnumerable in questo caso, ed enumerare su di esso. Questo aggiungerebbe una copia di ogni elemento in ogni enumerabile interna a quella enumerazione.

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

Come detto, ma con un esempio di codice:

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

Per illustrare la risposta di Nippysaurus: Se avete intenzione di Aggiungi i nuovi elementi alla lista e si desidera elaborare gli elementi appena aggiunti anche nel corso della stessa enumerazione allora si può solo utilizzare per anello anziché foreach ciclo, problema risolto:)

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

Ad esempio eseguibile:

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

Output di ultimo esempio:

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

Lista (15 articoli)
0
1 Pagina 2 Pagina 3 Pagina 4
5 Pagina 6 Pagina 7 Pagina 8 Pagina 9
1 Pagina 3
5 Pagina 7 Pagina 9

Ecco come si può fare (soluzione rapida e sporca Se davvero bisogno di questo tipo di comportamento, si dovrebbe riconsiderare il disegno o la priorità su tutti i membri IList<T> e aggregare l'elenco di origine.):

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 è molto efficace per la giocoleria con le collezioni.

I tuoi tipi e la struttura non sono chiare per me, ma cercherò di adattare il vostro esempio al meglio delle mie capacità.

dal codice sembra che, per ogni articolo, si aggiunge a tale elemento tutto da sua proprietà 'Enumerable'. Questo è molto semplice:

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

Come esempio più generale, diciamo che vogliamo iterare una collezione e rimuovere gli elementi in cui una determinata condizione è vera. Evitando foreach, utilizzando LINQ:

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

Aggiungi un elemento basato su ogni elemento esistente? Nessun problema:

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

Non è possibile modificare la raccolta enumerabile mentre viene enumerato, in modo da avere per apportare le modifiche prima o dopo aver enumerato.

Il ciclo for è una bella alternativa, ma se la vostra collezione IEnumerable non implementa ICollection, non è possibile.

In entrambi:

1) Copia di raccolta prima. Enumerare la raccolta copiato e modificare la raccolta originale durante l'enumerazione. (@Tvanfosson)

o

2) Mantenere un elenco di modifiche e impegnarli dopo l'enumerazione.

L'approccio migliore dal punto di vista delle prestazioni è probabilmente quello di utilizzare un uno o due array. Copiare l'elenco in un array, fare operazioni sulla matrice, e poi costruire un nuovo elenco dalla matrice. Accesso a un elemento di matrice è più veloce di accedere a un elemento dell'elenco e conversioni tra un List<T> e T[] può utilizzare un facile operazione "copia di massa" che evita il sovraccarico associato l'accesso degli articoli.

Ad esempio, supponiamo di avere un List<string> e desiderano avere ogni stringa nella lista che inizia con T essere seguito da una voce "Boo", mentre ogni stringa che inizia con "U" è caduto del tutto. Un approccio ottimale sarebbe probabilmente qualcosa di simile:

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
}

Sarebbe stato utile se List<T> fornito un metodo per costruire una lista da una porzione di una matrice, ma io sono a conoscenza di alcun metodo efficace per farlo. Eppure, le operazioni sugli array sono abbastanza veloce. Degno di nota è il fatto che l'aggiunta e la rimozione di elementi dalla lista non richiede "spingere" intorno ad altri oggetti; ogni elemento viene scritto direttamente al suo punto appropriato nella matrice.

Si dovrebbe usare for() invece di foreach() in questo caso.

Per aggiungere alla risposta LINQ di Timo può essere utilizzato in questo modo, così:

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);

Ho scritto un passo facile, ma a causa di questa performance sarà degradato

Qui è il mio frammento di codice:-

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;
                                }
                            }
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top