it-swarm.com.de

Was ist der beste Weg, um eine Liste in einer 'foreach'-Schleife zu ändern?

Eine neue Funktion in C #/.NET 4.0 besteht darin, dass Sie Ihre Aufzählungszeichen in einem foreach ändern können, ohne die Ausnahme zu erhalten. Siehe Paul Jacksons Blogeintrag Eine interessante Nebenwirkung der Parallelität: Entfernen von Elementen aus einer Sammlung beim Auflisten für Informationen zu dieser Änderung.

Was ist der beste Weg, um Folgendes zu tun?

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

Normalerweise verwende ich eine IList als Cache/Puffer bis zum Ende der foreach, aber gibt es einen besseren Weg?

60
Polo

Die in foreach verwendete Sammlung ist unveränderlich. Dies ist sehr beabsichtigt. 

Wie heißt es auf MSDN :

Die foreach-Anweisung wird für .__ verwendet. iteriere durch die Sammlung, um .__ zu erhalten. die Informationen, die Sie möchten, können aber darf nicht zum Hinzufügen oder Entfernen von Elementen verwendet werden aus der Quellensammlung zu vermeiden unvorhersehbare Nebenwirkungen. Wenn du Sie müssen Elemente hinzufügen oder aus der .__ entfernen. Quellensammlung verwenden Sie eine for-Schleife.

Der von Poko bereitgestellte Beitrag in link zeigt an, dass dies in den neuen gleichzeitigen Sammlungen zulässig ist. 

65
Rik

Erstellen Sie eine Kopie der Enumeration, und verwenden Sie in diesem Fall eine IEnumerable-Erweiterungsmethode, und listen Sie sie auf. Dies würde eine Kopie jedes Elements in jeder inneren Aufzählung dieser Aufzählung hinzufügen.

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

Wie erwähnt, aber mit einem Codebeispiel:

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

Um die Antwort von Nippysaurus zu veranschaulichen: Wenn Sie hinzufügen die neuen Elemente zur Liste hinzufügen und die neu hinzugefügten Elemente auch während derselben Aufzählung verarbeiten möchten, können Sie for loop statt foreach verwenden Schleife, Problem gelöst :)

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

Für ein lauffähiges Beispiel:

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

Ausgabe des letzten Beispiels:

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

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

6
Roland Pihlakas

So können Sie dies tun (schnelle und schmutzige Lösung. Wenn Sie wirklich diese Art von Verhalten benötigen, sollten Sie entweder Ihr Design überdenken oder alle IList<T>-Mitglieder überschreiben und die Quellliste zusammenfassen):

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);
        }
    }
}
3
Anton Gogolev

LINQ ist sehr effektiv beim Jonglieren mit Sammlungen.

Ihre Art und Struktur sind für mich unklar, aber ich werde versuchen, Ihr Beispiel so gut es geht zu passen.

Aus Ihrem Code ergibt sich, dass Sie für jedes Element alles aus seiner eigenen Enumerable-Eigenschaft hinzufügen. Das ist sehr einfach:

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

Als allgemeines Beispiel möchten wir eine Sammlung durchlaufen und Elemente entfernen, bei denen eine bestimmte Bedingung zutrifft. foreach mit LINQ vermeiden:

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

Hinzufügen eines Artikels basierend auf jedem vorhandenen Artikel? Kein Problem:

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

Sie können die aufzuzählende Auflistung nicht während der Aufzählung ändern. Daher müssen Sie Ihre Änderungen vor oder nach der Aufzählung vornehmen.

Die for-Schleife ist eine nette Alternative, aber wenn Ihre IEnumerable-Sammlung ICollection nicht implementiert, ist dies nicht möglich.

Entweder: 

1) Zuerst die Sammlung kopieren. Zählen Sie die kopierte Sammlung auf und ändern Sie die ursprüngliche Sammlung während der Aufzählung. (@tvanfosson)

oder

2) Führen Sie eine Liste der Änderungen ein und legen Sie sie nach der Aufzählung fest.

1
Josh G

Aus Performance-Sicht ist es wahrscheinlich der beste Ansatz, ein oder zwei Arrays zu verwenden. Kopieren Sie die Liste in ein Array, führen Sie Vorgänge für das Array aus und erstellen Sie dann eine neue Liste aus dem Array. Der Zugriff auf ein Array-Element ist schneller als der Zugriff auf ein Listenelement. Konvertierungen zwischen einem List<T> und einem T[] können eine schnelle "Massenkopie" -Operation verwenden, wodurch der mit dem Zugriff auf einzelne Elemente verbundene Overhead vermieden wird.

Angenommen, Sie haben einen List<string> und möchten, dass jeder String in der Liste, der mit T beginnt, von einem Element "Boo" gefolgt wird, während jeder String, der mit "U" beginnt, vollständig gelöscht wird. Ein optimaler Ansatz wäre wahrscheinlich so etwas wie:

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
}

Es wäre hilfreich gewesen, wenn List<T> eine Methode zum Erstellen einer Liste aus einem Teil eines Arrays zur Verfügung gestellt hätte, mir ist jedoch keine effiziente Methode bekannt. Der Betrieb von Arrays ist jedoch ziemlich schnell. Bemerkenswert ist die Tatsache, dass das Hinzufügen und Entfernen von Elementen aus der Liste kein "Schieben" um andere Elemente erfordert; Jedes Element wird direkt an die entsprechende Stelle im Array geschrieben.

1
supercat

Um Timos Antwort hinzuzufügen, kann LINQ auch folgendermaßen verwendet werden:

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

In diesem Fall sollten Sie for() anstelle von foreach() verwenden.

0
Nippysaurus

Ich habe einen einfachen Schritt geschrieben, aber aufgrund dessen wird die Leistung beeinträchtigt

Hier ist mein Code-Ausschnitt: -

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;
                                }
                            }
0
pravin ghare