foreach con lista genérica, detectando la primera iteración cuando se usa el tipo de valor

Al foreach en una lista genérica, a menudo quiero hacer algo diferente para el primer elemento de la lista:

 List objs = new List { new Object(), new Object(), new Object(), new Object() }; foreach (object o in objs) { if (o == objs.First()) { System.Diagnostics.Debug.WriteLine("First object - do something special"); } else { System.Diagnostics.Debug.WriteLine("object Do something else"); } } 

Esto dará como resultado:

     Primer objeto - hacer algo especial
     objeto hacer otra cosa
     objeto hacer otra cosa
     objeto hacer otra cosa

Todo esto está bien y dandy.

Sin embargo, si mi lista genérica es de un tipo de valor, este enfoque fallará.

 List ints = new List { 0, 0, 0, 0 }; foreach (int i in ints) { if (i == ints.First()) { System.Diagnostics.Debug.WriteLine("First int - do something special"); } else { System.Diagnostics.Debug.WriteLine("int Do something else"); } } 

Esto dará como resultado:

     Primera int - hacer algo especial
     Primera int - hacer algo especial
     Primera int - hacer algo especial
     Primera int - hacer algo especial

Ahora sé que podría recodificar esto para agregar una variable de bandera boolean o tradicional for bucle, pero me pregunto si hay alguna manera de averiguar si un bucle foreach está en la primera iteración de su bucle.

Bueno, podrías codificarlo usando iteración explícita:

 using(var iter = ints.GetEnumerator()) { if(iter.MoveNext()) { // do "first" with iter.Current while(iter.MoveNext()) { // do something with the rest of the data with iter.Current } } } 

La opción de bandera bool (con foreach ) es probablemente más fácil aunque … ¡eso es lo que (casi) siempre hago!

Otra opción sería LINQ:

 if(ints.Any()) { var first = ints.First(); // do something with first } foreach(var item in ints.Skip(1)) { // do something with the rest of them } 

La desventaja de lo anterior es que trata de mirar la lista 3 veces … ya que sabemos que es una lista, está bien, pero si todo lo que tuviéramos fuera un IEnumerable , solo sería razonable iterar una vez (ya que la fuente podría no ser re-legible).

Hace un tiempo escribí SmartEnumerable (parte de MiscUtil) que le permite saber si el elemento actual es el primero o el último, así como su índice. Eso puede ayudarlo … es parte de MiscUtil, que es de código abierto. Por supuesto, puede tomar solo SmartEnumerable con la misma licencia.

Código de muestra (c’n’p de la página web):

 using System; using System.Collections.Generic; using MiscUtil.Collections; class Example { static void Main(string[] args) { List list = new List(); list.Add("a"); list.Add("b"); list.Add("c"); list.Add("d"); list.Add("e"); foreach (SmartEnumerable.Entry entry in new SmartEnumerable(list)) { Console.WriteLine ("{0,-7} {1} ({2}) {3}", entry.IsLast ? "Last ->" : "", entry.Value, entry.Index, entry.IsFirst ? "<- First" : ""); } } } 

EDITAR: tenga en cuenta que, si bien funciona con tipos de referencia con referencias distintas, aún fallará si le da una lista en la que la primera referencia aparezca en otro lugar de la lista.

 foreach(int i in ints.Take(1)) { //do first thing } foreach(int i in ints.Skip(1)) { //do other things } 

Como ya estás usando LINQ, puedes hacer esto:

 var list = new List(); // do something with list.First(); foreach (var item in list.Skip(1)) { // do other stuff } 

El problema que tiene es que la igualdad para los tipos de valor se basa en el valor real, no en la referencia en sí. Específicamente, en su ejemplo, su lista es todos ceros, por lo que está preguntando si currentItem == firstItem, y como ambos son cero, esto siempre es cierto. Para este tipo de cosas, siempre he terminado usando una bandera booleana.

En lugar de if (i == ints.First()) , ¿funciona si usa if (i.Equals(ints.First()) ?

Si realmente no quieres usar un booleano, puedes hacer algo como:

 List ints = new List { 0, 0, 0, 0 }; System.Diagnostics.Debug.WriteLine("First int - do something special" + ints.FirstOrDefault().ToString()); foreach (int i in ints.Skip(1)) { { System.Diagnostics.Debug.WriteLine("int Do something else"); } } 

Pero eso parece un poco … extraño 🙂

Editando forma de otra respuesta:

 IList objs = new List { new Object(), new Object(), new Object(), new Object() }; // to access first element: objs[0] // to access last element: objs[objs.Count - 1] System.Diagnostics.Debug.WriteLine("First object - do something special"); foreach (object o in objs.Skip(1)) { System.Diagnostics.Debug.WriteLine("object Do something else"); } 

Enlace de referencia sobre:

Uso de Skip : Enumerable.Skip Method (IEnumerable, Int32)

Usando IList : Lista o IList