item - .NET-Eliminar de una lista<T> dentro de un bucle ''foreach''
remove object from list c# (17)
¿Realmente necesitas hacer esto dentro de un bucle foreach
?
Esto logrará los mismos resultados que sus ejemplos, es decir, eliminar todos los elementos de la lista hasta el primer elemento que coincida con la condición (o eliminar todos los elementos si ninguno de ellos coincide con la condición).
int index = Os.FindIndex(x => x.cond);
if (index > 0)
Os.RemoveRange(0, index);
else if (index == -1)
Os.Clear();
Tengo un código que quiero lucir así:
List<Type> Os;
...
foreach (Type o in Os)
if (o.cond)
return; // Quitting early is important for my case!
else
Os.Remove(o);
... // Other code
Esto no funciona, porque no puede eliminar de la lista cuando está dentro de un bucle foreach
sobre esa lista:
¿Hay una manera común de resolver el problema?
Puedo cambiar a un tipo diferente si es necesario.
Opcion 2:
List<Type> Os;
...
while (Os.Count != 0)
if (Os[0].cond)
return;
else
Os.RemoveAt(0);
... // Other code
Feo, pero debería funcionar.
Acabo de tener el mismo problema y lo resolví utilizando lo siguiente:
foreach (Type o in (new List(Os))) { if (something) Os.Remove(o); }
Se itera a través de una copia de la lista y se elimina de la lista original.
Acabo de tener ese problema con mi biblioteca de análisis. Intenté esto:
for (int i = 0; i < list.Count; i++)
{
if (/*condition*/)
{
list.RemoveAt(i);
i--;
}
}
Es bastante simple, pero no he pensado en ningún punto de ruptura.
Actualización: Añadido para completar
Como han respondido varios, no debe modificar una colección mientras la itera con GetEnumerator () (por ejemplo, foreach
). El marco le impide hacer esto lanzando una excepción. La solución genérica a esto es iterar "manualmente" con for
(ver otras respuestas). Tenga cuidado con su índice para no omitir elementos ni volver a evaluar el mismo dos veces (utilizando i--
o iterando hacia atrás).
Sin embargo, para su caso específico, podemos optimizar las operaciones de eliminación ... respuesta original a continuación.
Si lo que desea es eliminar todos los elementos hasta que uno cumpla con una condición determinada (eso es lo que hace su código), puede hacer esto:
bool exitCondition;
while(list.Count > 0 && !(exitCondition = list[0].Condition))
list.RemoveAt(0);
O si desea utilizar una sola operación de eliminación:
SomeType exitCondition;
int index = list.FindIndex(i => i.Condition);
if(index < 0)
list.Clear();
else
{
exitCondition = list[0].State;
list.RemoveRange(0, count);
}
Nota: dado que estoy asumiendo ese elemento. La item.Condition
es bool
, estoy usando item.State
para guardar la condición de salida.
Actualización: se agregaron límites de verificación y se guardaron las condiciones de salida en ambos ejemplos
Agregue el elemento para eliminar en una lista y luego elimine estos elementos utilizando RemoveAll
:
List<Type> Os;
List<Type> OsToRemove=new List<Type>();
...
foreach (Type o in Os){
if (o.cond)
return;
else
OsToRemove.Add(o);
}
Os.RemoveAll(o => OsToRemove.Contains(o));
Aquí está la SOLUCIÓN MÁS FÁCIL con el POR QUÉ más simple.
PROBLEMA:
Normalmente, estamos eliminando de la lista original, esto produce el problema de mantener el recuento de la lista y la ubicación del iterador.
List<Type> Os = ....;
Os.ForEach(
delegate(Type o) {
if(!o.cond) Os.Remove(o);
}
);
SOLUCIÓN - LINQ.ForEach
:
Tenga en cuenta que todo lo que he añadido es ToList()
. Esto crea una nueva lista en la que realiza ForEach; por lo tanto, puede eliminar su lista original y seguir recorriendo toda la lista.
List<Type> Os = ....;
Os.ToList().ForEach(
delegate(Type o) {
if(!o.cond) Os.Remove(o);
}
);
SOLUCIÓN - foreach
regular:
Esta técnica también funciona para las declaraciones regulares de foreach
.
List<Type> Os = ....;
foreach(Type o in Os.ToList()) {
if(!o.cond) Os.Remove(o);
}
Tenga en cuenta que esta solución no funcionará si su Lista original contiene un elemento de struct
.
Hay una buena discusión de esto en Eliminar elementos de una lista mientras se repite en ella .
Proponen:
for(int i = 0; i < count; i++)
{
int elementToRemove = list.Find(<Predicate to find the element>);
list.Remove(elementToRemove);
}
Intentaría encontrar el índice del primer elemento que no satisface el predicado y hacer RemoveRange (0, índice) en él. Si nada más, debería haber menos Eliminar llamadas.
La solución de Anzurio es probablemente la más directa, pero aquí hay otra limpia, si no le importa agregar un montón de interfaces / clases a su biblioteca de utilidades.
Puedes escribirlo así
List<Type> Os;
...
var en = Os.GetRemovableEnumerator();
while (en.MoveNext())
{
if (en.Current.Cond)
en.Remove();
}
Ponga la siguiente infraestructura, inspirada en el Iterator<T>.remove
de Java Iterator<T>.remove
, en su biblioteca de utilidades:
static class Extensions
{
public static IRemovableEnumerator<T> GetRemovableEnumerator<T>(this IList<T> l)
{
return new ListRemovableEnumerator<T>(l);
}
}
interface IRemovableEnumerator<T> : IEnumerator<T>
{
void Remove();
}
class ListRemovableEnumerator<T> : IRemovableEnumerator<T>
{
private readonly IList<T> _list;
private int _count;
private int _index;
public ListRemovableEnumerator(IList<T> list)
{
_list = list;
_count = list.Count;
_index = -1;
}
private void ThrowOnModification()
{
if (_list.Count != _count)
throw new InvalidOperationException("List was modified after creation of enumerator");
}
public void Dispose()
{
}
public bool MoveNext()
{
ThrowOnModification();
if (_index + 1 == _count)
return false;
_index++;
return true;
}
public void Reset()
{
ThrowOnModification();
_index = -1;
}
object IEnumerator.Current
{
get { return Current; }
}
public T Current
{
get { return _list[_index]; }
}
public void Remove()
{
ThrowOnModification();
_list.RemoveAt(_index);
_index--;
_count--;
}
}
Mira Enumerable.SkipWhile()
Enumerable.SkipWhile( x => condition).ToList()
Generalmente no mutar una lista, hace que sea mucho más fácil vivir. :)
Nunca debe eliminar nada de una colección sobre la que esté iterando dentro de un bucle foreach. Básicamente es como cortar la rama en la que estás sentado.
Utilice su alternativa al tiempo. Es el camino a seguir.
Puedes recorrer la lista hacia atrás:
for (int i = myList.Count - 1; i >= 0; i--)
{
if (whatever) myList.RemoveAt(i);
}
En respuesta a su comentario sobre el deseo de dejar de fumar cuando encuentra un elemento que NO está eliminando, entonces usar un bucle while sería la mejor solución.
Sé que pediste algo más, pero si quieres eliminar condicionalmente un montón de elementos, puedes usar la expresión lambda:
Os.RemoveAll(o => !o.cond);
Si sabes que tu lista no es muy grande puedes usar
foreach (Type o in new List<Type>(Os))
....
lo que creará un duplicado temporal de la lista. Su llamada remove () no interferirá con el iterador.
Soy un programador de Java, pero algo como esto funciona:
List<Type> Os;
List<Type> Temp;
...
foreach (Type o in Os)
if (o.cond)
Temp.add(o);
Os.removeAll(Temp);
puedes hacerlo con linq
MyList = MyList.Where(x=>(someCondition(x)==true)).ToList()
Os.RemoveAll(delegate(int x) { return /// });