221 votos

Bucle foreach, determinar cuál es la última iteración del bucle

Tengo un bucle foreach y necesito ejecutar cierta lógica cuando se elige el último elemento de la List, por ejemplo:

 foreach (Item result in Model.Results)
 {
      //si el resultado actual es el último elemento en Model.Results
      //entonces hacer algo en el código
 }

¿Puedo saber cuál es el último bucle sin usar un bucle for y contadores?

271voto

ChrisF Puntos 74295

Si solo necesita hacer algo con el último elemento (en lugar de algo diferente con el último elemento, entonces usar LINQ ayudará aquí:

Item last = Model.Results.Last();
// hacer algo con el último

Si necesita hacer algo diferente con el último elemento, entonces necesitaría algo como:

Item last = Model.Results.Last();
foreach (Item result in Model.Results)
{
    // hacer algo con cada elemento
    if (result.Equals(last))
    {
        // hacer algo diferente con el último elemento
    }
    else
    {
        // hacer algo diferente con todos los elementos excepto el último
    }
}

Aunque probablemente necesitaría escribir un comparador personalizado para asegurarse de que podría decir que el elemento era el mismo que el elemento devuelto por Last().

Este enfoque debe usarse con precaución ya que Last probablemente tenga que iterar a través de la colección. Si bien esto puede no ser un problema para colecciones pequeñas, si se vuelve grande podría tener implicaciones en el rendimiento. También fallará si la lista contiene elementos duplicados. En estos casos, algo como esto puede ser más apropiado:

int totalCount = result.Count();
for (int count = 0; count < totalCount; count++)
{
    Item result = Model.Results[count];

    // hacer algo con cada elemento
    if ((count + 1) == totalCount)
    {
        // hacer algo diferente con el último elemento
    }
    else
    {
        // hacer algo diferente con cada elemento excepto el último
    }
}

172voto

¿Qué tal un buen bucle for clásico?

for (int i = 0; i < Model.Results.Count; i++) {

     if (i == Model.Results.Count - 1) {
           // este es el último elemento
     }
}

O usando Linq y el foreach:

foreach (Item result in Model.Results)   
{   
     if (Model.Results.IndexOf(result) == Model.Results.Count - 1) {
             // este es el último elemento
     }
}

42voto

Shimmy Puntos 23393

¡Usar Last() en ciertos tipos recorrerá toda la colección!
Lo que significa que si haces un foreach y llamas a Last(), ¡habrás recorrido la colección dos veces!, lo cual seguramente te gustaría evitar en colecciones grandes.

Entonces la solución es usar un bucle while:

using var enumerator = collection.GetEnumerator();

var last = !enumerator.MoveNext();
T current;

while (!last) 
{ 
  current = enumerator.Current;

  // procesar ítem

  last = !enumerator.MoveNext(); 
  if(last) 
  {
    // procesamiento adicional para el último ítem
  }
} 

Así que a menos que el tipo de colección sea de tipo IList, la función Last() iterará a través de todos los elementos de la colección.

Prueba

Si tu colección proporciona acceso aleatorio (por ejemplo, implementa IList), también puedes verificar tu ítem de la siguiente manera.

if(collection is IList list) 
  return collection[^1]; // reemplazar con collection.Count -1 en aplicaciones anteriores a C#8

39voto

KeithS Puntos 36130

Como muestra Chris, Linq funcionará; simplemente usa Last() para obtener una referencia al último elemento en el enumerable, y siempre y cuando no estés trabajando con esa referencia entonces haz tu código normal, pero si ESTÁS trabajando con esa referencia entonces haz tu cosa extra. Su inconveniente es que siempre será de complejidad O(N).

En su lugar, puedes usar Count() (que es O(1) si el IEnumerable es también un ICollection; esto es cierto para la mayoría de los IEnumerables comunes integrados), e híbrida tu foreach con un contador:

var i=0;
var count = Model.Results.Count();
foreach (Item result in Model.Results)
{
    if (++i == count) //este es el último elemento
}

7voto

Matthias Meid Puntos 8473

La implementación del iterador no proporciona eso. Su colección podría ser un IList que es accesible a través de un índice en O(1). En ese caso, puede utilizar un for normal:

for(int i = 0; i < Model.Results.Count; i++)
{
  if(i == Model.Results.Count - 1) doMagic();
}

Si conoce el recuento, pero no puede acceder a través de índices (por lo tanto, el resultado es una ICollection), puede contar usted mismo incrementando un i en el cuerpo del foreach y comparándolo con la longitud.

Todo esto no es perfectamente elegante. La solución de Chris puede ser la más bonita que he visto hasta ahora.

Iteramos.com

Iteramos es una comunidad de desarrolladores que busca expandir el conocimiento de la programación mas allá del inglés.
Tenemos una gran cantidad de contenido, y también puedes hacer tus propias preguntas o resolver las de los demás.

Powered by:

X