viernes, 2 de abril de 2010

C# – Eliminar ítems en una lista

 

Introducción

Existen muchas formas de trabajar con una lista, y realizar operaciones de búsqueda y eliminación de aquellos elementos que debe ser filtrados, pero hay que tener cuidado ya que no todas las operaciones son validas

En este articulo se analizaría las formas de buscar un elemento y removerlo de la colección, analizando además métodos no recomendados

El problema

Cuando se tiene que realizar una operación sobre una lista, filtrando sus datos, lo primero que viene a la mente es recorrerla y allí mismo eliminar los ítems que no coinciden con la búsqueda.

O sea implementar algo similar a esto:

private void btnEliminarConProblema_Click(object sender, EventArgs e)
{
    List<Producto> list = ObtenerLista();

    foreach (Producto item in list)
    {
        if (!item.Descripcion.ToLower().Contains(txtBusqueda.Text.ToLower()))
            list.Remove(item);
    }

    LoadListView(list);
}

Pero esta primera aproximación presenta un defecto técnico, resulta que las listas necesita mantener el índice intacto para poder mantener el ciclo, el cual estamos alterando si removemos un ítem.

En este caso veremos un error con el mensaje:

Collection was modified; enumeration operation may not execute.

Soluciones

Existe una buen variedad de soluciones a este problema, en donde la implementación de cada una dependerá del objetivo que se quiera alcanzar

Una de ellas podría ser implementando el ciclo por medio de un simple for

[Usando el For]

private void btnEliminarConFor_Click(object sender, EventArgs e)
{
    List<Producto> list = ObtenerLista();

    for (int i = 0; i < list.Count; i++)
    {
        if (!list[i].Descripcion.ToLower().Contains(txtBusqueda.Text.ToLower()))
        {
            list.RemoveAt(i);
            i--;
        }
    }

    LoadListView(list);
}

Como se observa este requiere trabar con los índices de la colección, pero para poder mantenerlo actualizado es preciso restar uno cuando un ítem es quitado.

Otra alternativa podrías ser usar una lista auxiliar que acumule los ítems que no coinciden con la búsqueda, para ser eliminado en un segundo ciclo de la lista

[Usando una lista auxiliar]

private void btnEliminarConColeccionAuxiliar_Click(object sender, EventArgs e)
{
    List<Producto> listAux = new List<Producto>();
    List<Producto> list = ObtenerLista();

    foreach (Producto item in list)
    {
        if (!item.Descripcion.ToLower().Contains(txtBusqueda.Text.ToLower()))
        {
            listAux.Add(item);
            continue;
        }

    }

    foreach (Producto item in listAux)
    {
        list.Remove(item);
    }

    LoadListView(list);

}

El inconveniente con esta aproximación es que se necesitan dos ciclos para terminar la tarea.

También existen alternativas algo mas performantes, como ser el uso de delegados, expresiones Lambda, o Linq

[Usando Delegados]

private void btnEliminarConDelegate_Click(object sender, EventArgs e)
{
    List<Producto> list = ObtenerLista();

    //
    // quitara aquellos productos en donde la funcion
    // definida en el delegado retorne un true
    //
    list.RemoveAll(delegate(Producto prod)
    {
        return !prod.Descripcion.ToLower().Contains(txtBusqueda.Text.ToLower());
    });


    LoadListView(list);

}

[Usando Expresiones Lambda]

private void btnEliminarConLambda_Click(object sender, EventArgs e)
{
    List<Producto> list = ObtenerLista();

    list.RemoveAll(x => !x.Descripcion.ToLower().Contains(txtBusqueda.Text.ToLower()));

    LoadListView(list);
}

[Usando Linq]

private void btnEliminarConLinq_Click(object sender, EventArgs e)
{
    List<Producto> list = ObtenerLista();

    list = (from item in list
            where item.Descripcion.ToLower().Contains(txtBusqueda.Text.ToLower())
            select item).ToList<Producto>();

    LoadListView(list);
}

 

[C#]
[VB.NET]

6 comentarios:

Diego dijo...

Gracias Leandro, me he topado con tus ayudas en toda la red, es un gran trabajo el que realizas, otra vez una ayuda tuya me soluciona un problema.
Gracias otra vez y saludos desde Perú!!

Ccarlos dijo...

Saludos Leandro

Tengo una lista y deseo poder eliminar un elemento algoi así como

var upd = DT1 .Where (c => c.id == myId).Update (d => d.nombre = otroNombre) ;

Gracias desde ya

David

Leandro Tuttini dijo...

hola Carlos

si la idea es usa lambda porque no usas

DT1.RemoveAll(c=>c.id == myId)

con eso remueves los items que coincidan con la comparacion

saludos

William Jared Serrano ortez dijo...

Hola leandro, tu código me parece excelente, estoy haciendo algo similar a tu código pero no encuentro la sintaxis correcta y talvez me puedes ayudar, tengo un dataset con 2 columnas una contiene el nombre de un archivo y la otra la ruta del archivo, ya tengo el bucle para extraer todas las rutas del dataset en string[], ahora lo que necesito es iterar en este array y comprobar que todos los archivos existen, en caso de que no exista me elimine la fila en el dataset de los archivos que no encuentre....

Muchas gracias de antemano

William Jared Serrano ortez dijo...

Estoy usando C#

SATRACK dijo...
Este comentario ha sido eliminado por el autor.