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]

12 comentarios:

  1. 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ú!!

    ResponderEliminar
  2. 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

    ResponderEliminar
  3. 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

    ResponderEliminar
  4. 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

    ResponderEliminar
  5. Este comentario ha sido eliminado por el autor.

    ResponderEliminar
  6. Hola Leandro,
    tus posts me ayudaron con todo lo que necesite! muy buenos aportes!
    Te queria consultar algo.
    Tengo un Gridview cargado con items de textboxs y quisiera eliminar uno solo o varios con Checkboxes que ya tengo en el gridview.

    protected void Borrar_Click(object sender, ImageClickEventArgs e)
    {
    foreach (GridViewRow item in grdArticulos.Rows)
    {
    System.Web.UI.WebControls.CheckBox chk = (System.Web.UI.WebControls.CheckBox)item.FindControl("chkDelete");
    if (chk != null)
    {
    if (chk.Checked)
    {
    DialogResult result;

    //result = MessageBox.Show(message, caption, buttons);
    result = MessageBox.Show("Desea eliminar el articulo?", "Advertencia", MessageBoxButtons.OK, MessageBoxIcon.Information, MessageBoxDefaultButton.Button1, MessageBoxOptions.DefaultDesktopOnly);


    if (result == System.Windows.Forms.DialogResult.No)
    {



    return;

    }
    else
    {
    //ACA DEBERIA BORRARLO, PERO NO SE HACERLO


    }

    }
    }
    }

    }

    espero puedas ayudarme!
    Gracias!

    ResponderEliminar
  7. Hola buenas tardes, me he topado con un caso similar, pero en vez de eliminar , lo que quiero es añadir un registro a la colección, lo intente de la manera similar como el que muestras, y si me los aggrega pero al llegar al final me marco el error de que la coleccion ha sido editada y no deja continuar, no se si pude explicarme correctamente.

    ResponderEliminar
    Respuestas
    1. hola
      Es que tanto para agregar o eliminar no puedes hacerlo en la lista o coleccion que estas iterando en el foreach, requieres de una lista auxiliar donde ir agregando y al final recorres esta lista auxiliar y agregas los items en la lista definitiva
      saludos

      Eliminar
    2. Este comentario ha sido eliminado por el autor.

      Eliminar
  8. Este comentario ha sido eliminado por el autor.

    ResponderEliminar
  9. Muchísimas gracias. Sin tu ayuda no hubiese comprendido que el error provenía de borrar un elemento de una lista en un foreach. Opté por eso hacerlo fuera de la siguiente manera:

    bool remover = false;
    Tipo borrarElemento = new Tipo();
    foreach (Tipo elemento in Lista)
    {
    if (elemento == elementoBuscado)
    {
    borrarElemento = elemento;
    remover = true;
    }
    }
    if (remover)
    {
    Lista.Remove(borrarElemento);
    }

    Nuevamente gracias por tu ayuda. Un saludo.

    ResponderEliminar