Pregunta

Una nueva característica en C # / .NET 4.0 es que se puede cambiar su enumerable en un foreach sin conseguir la excepción. Ver el blog de Paul Jackson Un efecto secundario interesante de concurrencia: Eliminando elementos de una colección al enumerar para obtener información sobre este cambio

.

¿Cuál es la mejor manera de hacer lo siguiente?

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

Por lo general uso un IList como caché / memoria intermedia hasta el final de la foreach, pero ¿hay una mejor manera?

¿Fue útil?

Solución

La colección se utiliza en foreach es inmutable. Esto es en gran medida por el diseño.

Como se dice en MSDN :

  

La instrucción foreach se utiliza para   iterar a través de la colección para obtener   la información que desea, pero puede   No puede utilizar para añadir o eliminar elementos   de la colección de origen para evitar   efectos secundarios impredecibles. Si   necesita añadir o eliminar elementos del   colección de origen, utilizar un bucle.

El mensaje en la href="http://www.lovethedot.net/2009/03/interesting-side-effect-of-concurrency.html" proporcionado por Poko indica que este está permitido en las nuevas colecciones concurrentes.

Otros consejos

Haga una copia de la enumeración, utilizando un método de extensión IEnumerable en este caso, y enumerar sobre ella. Esto añadiría una copia de cada elemento en cada enumerables interior para que la enumeración.

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

Como se ha mencionado, pero con un ejemplo de código:

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

Para ilustrar la respuesta de Nippysaurus: Si va a Añadir los nuevos elementos a la lista y desea procesar los elementos recién añadidos también durante la misma enumeración a continuación, puedes utilizar de bucle en lugar de foreach / strong> bucle <, problema resuelto:)

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

Por ejemplo ejecutable:

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

salida del último ejemplo:

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

Lista (15 artículos)
0
1 Página 2 página 3 página 4 página 5 página 6 página 7 página 8 página 9
1 página 3 página 5 página 7 página 9

Así es como se puede hacer eso (solución rápida y sucia Si realmente necesitan este tipo de comportamiento, se debe someter a nuevo su diseño o anular todos los miembros IList<T> y agregar la lista de fuentes.):

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 es muy eficaz para juegos malabares con colecciones.

Sus tipos y estructura son claras para mí, pero voy a tratar de adaptarse a su ejemplo, a lo mejor de mi capacidad.

A partir de su código, parece que, para cada artículo, que está añadiendo a ese elemento de todo, desde su propia propiedad 'Enumerable'. Esto es muy simple:

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

Como un ejemplo más general, digamos que queremos recorrer una colección y eliminar elementos en una determinada condición es verdadera. Evitar foreach, utilizando LINQ:

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

Añadir un elemento en función de cada elemento existente? No hay problema:

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

No se puede cambiar la colección numerable mientras se está empadronado, por lo que tendrá que hacer los cambios antes o después de enumerar.

El bucle for es una buena alternativa, pero si su colección IEnumerable no implementa ICollection, no es posible.

O bien:

1) Copia primera colección. Enumerar la colección de copiado y cambie la colección original durante la enumeración. (@Tvanfosson)

o

2) Mantener una lista de cambios y comprometerse ellos después de la enumeración.

El mejor enfoque desde una perspectiva de rendimiento es probablemente usar una o dos matrices. Copiar la lista a una matriz, hacer operaciones en la matriz, y luego construir una nueva lista de la matriz. Acceso a un elemento de la matriz es más rápido que el acceso a un elemento de la lista, y las conversiones entre un List<T> y una T[] puede utilizar una operación rápida "de copia masiva" que evita la sobrecarga asociada acceder a artículos individuales.

Por ejemplo, suponga que tiene un List<string> y desea tener cada cadena de la lista que comienza con T ser seguido por un artículo "Boo", mientras que cada cadena que comienza con "U" se cae por completo. Un enfoque óptimo sería probablemente algo como:

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
}

Hubiera sido útil si List<T> proporciona un método para construir una lista de una porción de una matriz, pero estoy al tanto de cualquier método eficiente para hacerlo. Aún así, las operaciones en las matrices son bastante rápido. Es de destacar el hecho de que agregar y quitar elementos de la lista no requiere de "empujar" en torno a otros artículos; cada artículo se escribe directamente a su lugar apropiado en la matriz.

Debe utilizar realmente for() en lugar de foreach() en este caso.

Para añadir a la respuesta de LINQ Timo se puede utilizar como esto, así:

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

He escrito un paso fácil, pero debido a esto el rendimiento se degradará

Aquí está mi fragmento de código: -

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;
                                }
                            }
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top