Pregunta

Estoy buscando una mejor patrón para trabajar con una lista de elementos que cada necesidad procesa y luego, dependiendo del resultado se eliminan de la lista.

No se puede utilizar dentro de un .Remove(element) foreach (var element in X) (porque da lugar a excepción Collection was modified; enumeration operation may not execute.) ... tampoco se puede utilizar for (int i = 0; i < elements.Count(); i++) y .RemoveAt(i) porque interrumpe su posición actual en la colección relativa a i.

¿Hay una manera elegante de hacer esto?

¿Fue útil?

Solución

Iterar su lista en orden inverso con un bucle for:

for (int i = safePendingList.Count - 1; i >= 0; i--)
{
    // some code
    // safePendingList.RemoveAt(i);
}

Ejemplo:

var list = new List<int>(Enumerable.Range(1, 10));
for (int i = list.Count - 1; i >= 0; i--)
{
    if (list[i] > 5)
        list.RemoveAt(i);
}
list.ForEach(i => Console.WriteLine(i));

Como alternativa, se puede utilizar el RemoveAll método con un predicado a prueba contra:

safePendingList.RemoveAll(item => item.Value == someValue);

Este es un ejemplo simplificado de demostrar:

var list = new List<int>(Enumerable.Range(1, 10));
Console.WriteLine("Before:");
list.ForEach(i => Console.WriteLine(i));
list.RemoveAll(i => i > 5);
Console.WriteLine("After:");
list.ForEach(i => Console.WriteLine(i));

Otros consejos

Una solución simple y sencilla:

Usar un estándar para correr en circuito hacia atrás en su colección y RemoveAt(i) para eliminar elementos.

Invertir iteración debería ser el primero que viene a la mente cuando se quiere eliminar los elementos de una colección al iterar sobre ella.

Afortunadamente, hay una solución más elegante que escribir un bucle que implica escribir a máquina innecesario y puede ser propenso a errores.

ICollection<int> test = new List<int>(new int[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10});

foreach (int myInt in test.Reverse<int>())
{
    if (myInt % 2 == 0)
    {
        test.Remove(myInt);
    }
}
 foreach (var item in list.ToList()) {
     list.Remove(item);
 }

Si añadir ".ToList ()" a su lista (o los resultados de una consulta LINQ), puede quitar 'elemento' directamente 'de la lista' sin el temido " colección se modificó, la operación de enumeración no puede ejecutar ". error. El compilador hace una copia de "lista", por lo que se puede hacer con seguridad el eliminar en la matriz.

Si bien este patrón no es muy eficiente, que tiene un ambiente natural y es lo suficientemente flexible como para casi cualquier situación . Por ejemplo, cuando desea guardar todos los "elementos" a un DB y eliminarlo de la lista sólo cuando el DB salvación tiene éxito.

Uso de la ToArray () en una lista genérica le permite hacer una Remove (punto) en su lista genérica:

        List<String> strings = new List<string>() { "a", "b", "c", "d" };
        foreach (string s in strings.ToArray())
        {
            if (s == "b")
                strings.Remove(s);
        }

Seleccione los elementos que hacer que desee en lugar de tratar de eliminar los elementos que no desee. Esto es mucho más fácil (y en general más eficientes también) que la eliminación de elementos.

var newSequence = (from el in list
                   where el.Something || el.AnotherThing < 0
                   select el);

Yo quería publicar esto como un comentario en respuesta al comentario dejado por Michael Dillon más adelante, pero es demasiado largo y probablemente útil tener en mi respuesta de todos modos:

En lo personal, nunca me quito artículos uno por uno, si lo hace retiro de la necesidad, a continuación, llamar RemoveAll que tiene un predicado y sólo reordena la matriz interna una vez, mientras que Remove hace una operación Array.Copy para cada elemento se quita. RemoveAll es mucho más eficiente.

Y cuando esté al revés iteración en una lista, que ya tiene el índice del elemento que desea eliminar, por lo que sería mucho más eficiente a RemoveAt llamada, porque Remove hace primero un recorrido de la lista para encontrar el índice del elemento que está tratando de eliminar, pero ya se sabe que el índice.

Así que en general, no veo ninguna razón para Remove llamada nunca en un bucle para. Y lo ideal, si es posible, utilizar el código anterior para transmitir los elementos de la lista, según sea necesario para que nadie segunda estructura de datos tiene que ser creado en absoluto.

El uso de .ToList () hará una copia de su lista, como se explica en esta pregunta: ToList () - ¿Crea una nueva lista

Mediante el uso de ToList (), se puede eliminar de su lista original, porque en realidad estás interactuando sobre una copia.

foreach (var item in listTracked.ToList()) {    

        if (DetermineIfRequiresRemoval(item)) {
            listTracked.Remove(item)
        }

     }

Si la función que determina qué elementos que desea eliminar no tiene efectos secundarios y no muta el artículo (que es una función pura), una solución eficiente (tiempo lineal) simple y es:

list.RemoveAll(condition);

Si hay efectos secundarios, que haría uso de algo como:

var toRemove = new HashSet<T>();
foreach(var item in items)
{
     ...
     if(condition)
          toRemove.Add(item);
}
items.RemoveAll(toRemove.Contains);

Este es todavía el tiempo lineal, asumiendo que el hash es buena. Pero tiene un mayor uso de memoria debido a la hashset.

Por último, si su lista es solamente un IList<T> en lugar de un List<T> Sugiero mi respuesta a ¿Cómo puedo hacer esto iterador especial foreach . Esto tendrá tiempo de ejecución lineal dada implementaciones típicas de IList<T>, en comparación con el tiempo de ejecución cuadrática de muchas otras respuestas.

Como cualquier remove se toma en una condición que puede utilizar

list.RemoveAll(item => item.Value == someValue);
List<T> TheList = new List<T>();

TheList.FindAll(element => element.Satisfies(Condition)).ForEach(element => TheList.Remove(element));

No se puede usar foreach, pero se puede recorrer hacia delante y manejar la variable de índice del bucle cuando se quita un elemento, así:

for (int i = 0; i < elements.Count; i++)
{
    if (<condition>)
    {
        // Decrement the loop counter to iterate this index again, since later elements will get moved down during the remove operation.
        elements.RemoveAt(i--);
    }
}

Tenga en cuenta que en todas general de estas técnicas se basan en el comportamiento de la colección que se itera. La técnica que se muestra aquí trabajar con la lista estándar (T). (Es muy posible que escribir su propia clase de colección y iterador que hace permitir la extracción elemento durante un bucle foreach.)

El uso de Remove o RemoveAt en una lista, mientras que la iteración en esa lista intencionadamente se ha hecho difícil, porque es casi siempre lo que no debía hacer . Usted puede ser capaz de conseguir que funcione con un poco de truco inteligente, pero sería extremadamente lento. Cada vez que se llama Remove tiene que buscar a través de toda la lista para encontrar el elemento que desea eliminar. Cada vez que se llama RemoveAt tiene que mover elementos subsiguientes 1 posición hacia la izquierda. Como tal, cualquier solución utilizando Remove o RemoveAt, requeriría tiempo cuadrático, O (N $ ² $) .

Uso RemoveAll si es posible. De lo contrario, el siguiente patrón filtrará la lista in situ en el tiempo lineal, O (n) .

// Create a list to be filtered
IList<int> elements = new List<int>(new int[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10});
// Filter the list
int kept = 0;
for (int i = 0; i < elements.Count; i++) {
    // Test whether this is an element that we want to keep.
    if (elements[i] % 3 > 0) {
        // Add it to the list of kept elements.
        elements[kept] = elements[i];
        kept++;
    }
}
// Unfortunately IList has no Resize method. So instead we
// remove the last element of the list until: elements.Count == kept.
while (kept < elements.Count) elements.RemoveAt(elements.Count-1);

deseo el "patrón" era algo como esto:

foreach( thing in thingpile )
{
    if( /* condition#1 */ )
    {
        foreach.markfordeleting( thing );
    }
    elseif( /* condition#2 */ )
    {
        foreach.markforkeeping( thing );
    }
} 
foreachcompleted
{
    // then the programmer's choices would be:

    // delete everything that was marked for deleting
    foreach.deletenow(thingpile); 

    // ...or... keep only things that were marked for keeping
    foreach.keepnow(thingpile);

    // ...or even... make a new list of the unmarked items
    others = foreach.unmarked(thingpile);   
}

Esto alinear el código con el proceso que tiene lugar en el cerebro del programador.

Me reasignar la lista de una consulta LINQ que filtra los elementos que no desea conservar.

list = list.Where(item => ...).ToList();

A menos que la lista es muy grande no debe haber problemas de rendimiento significativas en hacer esto.

La mejor manera de eliminar elementos de una lista, mientras que la iteración en que es usar RemoveAll() . Pero la principal preocupación escrito por las personas es que tienen que hacer algunas cosas complejas dentro del bucle y / o tienen casos complejos comparar.

La solución es utilizar todavía RemoveAll() pero el uso de esta notación:

var list = new List<int>(Enumerable.Range(1, 10));
list.RemoveAll(item => 
{
    // Do some complex operations here
    // Or even some operations on the items
    SomeFunction(item);
    // In the end return true if the item is to be removed. False otherwise
    return item > 5;
});

Por supuesto de que predicado es una propiedad booleana de un elemento, que si bien es cierto, entonces el elemento debe ser eliminado:

        int i = 0;
        while (i < list.Count())
        {
            if (list[i].predicate == true)
            {
                list.RemoveAt(i);
                continue;
            }
            i++;
        }
foreach(var item in list.ToList())

{

if(item.Delete) list.Remove(item);

}

Simplemente crear una lista totalmente nueva de la primera. Digo "fácil" en lugar de "derecha" como la creación de una lista totalmente nueva viene probablemente con una prima de rendimiento con respecto al método anterior (no me he molestado con cualquier evaluación comparativa.) En general, prefiero este patrón, también puede ser útil para superar LINQ a Entidades limitaciones.

for(i = list.Count()-1;i>=0;i--)

{

item=list[i];

if (item.Delete) list.Remove(item);

}

esta manera ciclos a través de las hacia atrás lista con un viejo y simple bucle for. Hacer esto hacia delante podría ser problemático si el tamaño de los cambios de recolección, pero al revés siempre debe ser seguro.

Me encontré en una situación similar en el que tuve que quitar todos los n º en un List<T> dado.

for (int i = 0, j = 0, n = 3; i < list.Count; i++)
{
    if ((j + 1) % n == 0) //Check current iteration is at the nth interval
    {
        list.RemoveAt(i);
        j++; //This extra addition is necessary. Without it j will wrap
             //down to zero, which will throw off our index.
    }
    j++; //This will always advance the j counter
}

El coste de la eliminación de un elemento de la lista es proporcional a la cantidad de elementos siguiente que ha de ser eliminado. En el caso en que la primera mitad de los artículos calificar para el retiro, cualquier enfoque que se basa en la eliminación de elementos de forma individual terminará tener que realizar sobre N * N / 4 operaciones elemento en papel, que puede ser muy caro si la lista es grande .

Un enfoque más rápido es para escanear a través de la lista para encontrar el primer elemento a ser eliminado (si lo hay), y luego desde ese punto hacia adelante copiar cada elemento que debe ser retenido hasta el lugar que le corresponde. Una vez hecho esto, si los artículos R deben ser retenidos, los primeros artículos R en la lista serán aquellos artículos R, y todos los elementos que requieren su eliminación serán al final. Si esos elementos se eliminan en el orden inverso, el sistema no va a terminar tener que copiar cualquiera de ellos, por lo que si la lista tenía N elementos de los cuales artículos R, incluyendo la totalidad de la primera F, se retuvieron, será necesario copiar R-F artículos, y reducir el tamaño de la lista por un elemento de veces N-R. Todo el tiempo lineal.

Mi enfoque es que por primera vez se crea una lista de índices, que debe quedar eliminada. Después de bucle I en los índices y eliminar los elementos de la lista inicial. Esto se parece a esto:

var messageList = ...;
// Restrict your list to certain criteria
var customMessageList = messageList.FindAll(m => m.UserId == someId);

if (customMessageList != null && customMessageList.Count > 0)
{
    // Create list with positions in origin list
    List<int> positionList = new List<int>();
    foreach (var message in customMessageList)
    {
        var position = messageList.FindIndex(m => m.MessageId == message.MessageId);
        if (position != -1)
            positionList.Add(position);
    }
    // To be able to remove the items in the origin list, we do it backwards
    // so that the order of indices stays the same
    positionList = positionList.OrderByDescending(p => p).ToList();
    foreach (var position in positionList)
    {
        messageList.RemoveAt(position);
    }
}

Copiar la lista que está iteración. A continuación, retire de la copia y el original Interate. Yendo hacia atrás es confuso y no funciona bien cuando un bucle en paralelo.

var ids = new List<int> { 1, 2, 3, 4 };
var iterableIds = ids.ToList();

Parallel.ForEach(iterableIds, id =>
{
    ids.Remove(id);
});

En C # una forma fácil es marcar las que desea borrar y luego crear una nueva lista para repetir ...

foreach(var item in list.ToList()){if(item.Delete) list.Remove(item);}  

o incluso más sencillo el uso de LINQ ....

list.RemoveAll(p=>p.Delete);

, pero vale la pena considerar si otras tareas o hilos tendrán acceso a la misma lista, al mismo tiempo que usted está ocupado eliminando, y tal vez usar un ConcurrentList lugar.

Traza los elementos que se eliminan con una propiedad, y eliminar todos después de proceso.

using System.Linq;

List<MyProperty> _Group = new List<MyProperty>();
// ... add elements

bool cond = true;
foreach (MyProperty currObj in _Group)
{
    if (cond) 
    {
        // SET - element can be deleted
        currObj.REMOVE_ME = true;
    }
}
// RESET
_Group.RemoveAll(r => r.REMOVE_ME);

Sólo quería añadir mis 2 centavos a esto en caso de que esto ayuda a nadie, he tenido un problema similar, pero tenía que quitar varios elementos de una lista de arreglo mientras estaba siendo repiten a lo largo. la más alta respuesta upvoted lo hizo por mí en su mayor parte hasta que me encontré con errores y se dio cuenta de que el índice era mayor que el tamaño de la lista de arreglo en algunos casos debido a múltiples elementos trataba de una sustracción, pero el índice del bucle no mantener realizar un seguimiento de ello. He arreglado esto con una simple comprobación:

ArrayList place_holder = new ArrayList();
place_holder.Add("1");
place_holder.Add("2");
place_holder.Add("3");
place_holder.Add("4");

for(int i = place_holder.Count-1; i>= 0; i--){
    if(i>= place_holder.Count){
        i = place_holder.Count-1; 
    }

// some method that removes multiple elements here
}
myList.RemoveAt(i--);

simples;
scroll top