¿Cómo obtengo el primer elemento de un IEnumerable <T> en .net?

157

A menudo quiero tomar el primer elemento de un IEnumerable<T>en .net, y no he encontrado una buena manera de hacerlo. Lo mejor que se me ocurrió es:

foreach(Elem e in enumerable) {
  // do something with e
  break;
}

¡Qué asco! Entonces, ¿hay una buena manera de hacer esto?

TimK
fuente

Respuestas:

242

Si puede usar LINQ puede usar:

var e = enumerable.First();

Sin embargo, esto generará una excepción si enumerable está vacío: en cuyo caso puede usar:

var e = enumerable.FirstOrDefault();

FirstOrDefault()regresará default(T)si el enumerable está vacío, lo que será nullpara los tipos de referencia o el 'valor cero' predeterminado para los tipos de valor.

Si no puede usar LINQ, entonces su enfoque es técnicamente correcto y no es diferente de crear un enumerador usando los métodos GetEnumeratory MoveNextpara recuperar el primer resultado (este ejemplo supone que enumerable es un IEnumerable<Elem>):

Elem e = myDefault;
using (IEnumerator<Elem> enumer = enumerable.GetEnumerator()) {
    if (enumer.MoveNext()) e = enumer.Current;
}

Joel Coehoorn mencionado .Single()en los comentarios; esto también funcionará si espera que su enumerable contenga exactamente un elemento; sin embargo, arrojará una excepción si está vacío o es más grande que un elemento. Hay un SingleOrDefault()método correspondiente que cubre este escenario de manera similar a FirstOrDefault(). Sin embargo, David B explica que SingleOrDefault()aún puede arrojar una excepción en el caso en que el enumerable contenga más de un elemento.

Editar: Gracias Marc Gravell por señalar que necesito deshacerme de mi IEnumeratorobjeto después de usarlo. Edité el ejemplo que no es de LINQ para mostrar la usingpalabra clave para implementar este patrón.

Erik Forbes
fuente
2
Vale la pena señalar que SingleOrDefault devolverá 1) El único elemento si solo hay un elemento. 2) nulo si no hay artículos. 3) lanzar una excepción si hay más de un artículo.
Amy B
Buena llamada David B - agregó una nota.
Erik Forbes
36

En caso de que esté utilizando .NET 2.0 y no tenga acceso a LINQ:

 static T First<T>(IEnumerable<T> items)
 {
     using(IEnumerator<T> iter = items.GetEnumerator())
     {
         iter.MoveNext();
         return iter.Current;
     }
 }

Esto debería hacer lo que está buscando ... utiliza genéricos para que pueda obtener el primer elemento en cualquier tipo IEnumerable.

Llámalo así:

List<string> items = new List<string>() { "A", "B", "C", "D", "E" };
string firstItem = First<string>(items);

O

int[] items = new int[] { 1, 2, 3, 4, 5 };
int firstItem = First<int>(items);

Puede modificarlo con la suficiente facilidad para imitar el método de extensión IEnumerable.ElementAt () de .NET 3.5:

static T ElementAt<T>(IEnumerable<T> items, int index)
{
    using(IEnumerator<T> iter = items.GetEnumerator())
    {
        for (int i = 0; i <= index; i++, iter.MoveNext()) ;
        return iter.Current;
    }
} 

Llamándolo así:

int[] items = { 1, 2, 3, 4, 5 };
int elemIdx = 3;
int item = ElementAt<int>(items, elemIdx);

Por supuesto, si lo tiene acceso a LINQ, entonces hay un montón de buenas respuestas ya publicado ...

BenAlabastro
fuente
Vaya, por supuesto que tienes razón, gracias. He corregido mis ejemplos.
BenAlabaster
Si estás en 2.0, tampoco tienes var.
recursivo
1
Bueno, supongo que si quieres ponerte nervioso, los declararé correctamente: P
BenAlabaster
Como alguien que está atrapado usando .NET 2.0, ¡esto fue muy apreciado! Prestigio.
kayleeFrye_onDeck
26

Bueno, no especificó qué versión de .Net está usando.

Suponiendo que tiene 3.5, otra forma es el método ElementAt:

var e = enumerable.ElementAt(0);
Adam Lassek
fuente
2
ElementAt (0), agradable y simple.
JMD
1

prueba esto

IEnumberable<string> aa;
string a = (from t in aa where t.Equals("") select t.Value).ToArray()[0];
daodao
fuente
0

Use FirstOrDefault o un bucle foreach como ya se mencionó. Se debe evitar buscar manualmente un enumerador y llamar a Current. foreach dispondrá de su enumerador por usted si implementa IDisposable. Al llamar a MoveNext y Current, debe eliminarlo manualmente (si corresponde).

Mouk
fuente
1
¿Qué evidencia hay de que se debe evitar el enumerador? Las pruebas de rendimiento en mi máquina indican que tiene una ganancia de rendimiento aproximada del 10% sobre cada foreach.
BenAlabaster
Depende de lo que estés enumerando. Un Erumerator podría cerrar la conexión a la base de datos, borrar y cerrar el identificador de archivo, liberar algunos objetos bloqueados, etc. Supongo que no eliminar un enumerador de alguna lista entera no será perjudicial.
Mouk
0

Si su IEnumerable no lo expone <T>y Linq falla, puede escribir un método usando la reflexión:

public static T GetEnumeratedItem<T>(Object items, int index) where T : class
{
  T item = null;
  if (items != null)
  {
    System.Reflection.MethodInfo mi = items.GetType()
      .GetMethod("GetEnumerator");
    if (mi != null)
    {
      object o = mi.Invoke(items, null);
      if (o != null)
      {
        System.Reflection.MethodInfo mn = o.GetType()
          .GetMethod("MoveNext");
        if (mn != null)
        {
          object next = mn.Invoke(o, null);
          while (next != null && next.ToString() == "True")
          {
            if (index < 1)
            {
              System.Reflection.PropertyInfo pi = o
                .GetType().GetProperty("Current");
              if (pi != null) item = pi
                .GetValue(o, null) as T;
              break;
            }
            index--;
          }
        }
      }
    }
  }
  return item;
}
CZahrobsky
fuente
0

también puedes probar la versión más genérica que te da el elemento i-ésimo

enumerable.ElementAtOrDefault (i));

Espero eso ayude

persona aleatoria en internet
fuente