Jak uzyskać pierwszy element z IEnumerable <T> w .net?


156

Często chcę pobrać pierwszy element z IEnumerable<T>.net i nie znalazłem na to fajnego sposobu. Najlepsze, co wymyśliłem, to:

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

Fuj! Czy jest na to dobry sposób?

Odpowiedzi:


241

Jeśli możesz używać LINQ, możesz użyć:

var e = enumerable.First();

Spowoduje to jednak zgłoszenie wyjątku, jeśli enumerable jest puste: w takim przypadku możesz użyć:

var e = enumerable.FirstOrDefault();

FirstOrDefault()zwróci, default(T)jeśli wyliczalny jest pusty, co będzie nulldotyczyło typów referencyjnych lub domyślnej wartości zerowej dla typów wartości.

Jeśli nie możesz użyć LINQ, Twoje podejście jest poprawne technicznie i nie różni się od tworzenia modułu wyliczającego przy użyciu metod GetEnumeratori w MoveNextcelu pobrania pierwszego wyniku (w tym przykładzie założono, że wyliczalne jest IEnumerable<Elem>):

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

Joel Coehoorn wspomniany .Single()w komentarzach; zadziała również, jeśli spodziewasz się, że wyliczalny element zawiera dokładnie jeden element - jednak zgłosi wyjątek, jeśli jest pusty lub większy niż jeden element. Istnieje odpowiednia SingleOrDefault()metoda, która obejmuje ten scenariusz w podobny sposób jak FirstOrDefault(). Jednak David B wyjaśnia, że SingleOrDefault()nadal może zgłosić wyjątek w przypadku, gdy wyliczalny element zawiera więcej niż jeden element.

Edycja: Dziękuję Marcowi Gravellowi za wskazanie, że muszę pozbyć się mojego IEnumeratorobiektu po jego użyciu - zmodyfikowałem przykład bez LINQ, aby wyświetlić usingsłowo kluczowe w celu zaimplementowania tego wzorca.


2
Warto zauważyć, że SingleOrDefault zwróci 1) Jedyny element, jeśli jest tylko jeden element. 2) nieważne, jeśli nie ma pozycji. 3) zgłoś wyjątek, jeśli istnieje więcej niż jeden element.
Amy B

Dobre połączenie David B - dodał notatkę.
Erik Forbes

36

Na wszelki wypadek, gdy używasz .NET 2.0 i nie masz dostępu do LINQ:

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

Powinno to zrobić to, czego szukasz ... używa typów ogólnych, aby uzyskać pierwszy element dowolnego typu IEnumerable.

Nazwij to tak:

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

Lub

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

Można go łatwo zmodyfikować, aby naśladować metodę rozszerzenia IEnumerable.ElementAt () .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;
    }
} 

Nazywając to tak:

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

Oczywiście, jeśli zrobić mieć dostęp do LINQ, to istnieje wiele dobrych odpowiedzi już pisał ...


Ups, oczywiście, że masz rację, dzięki. Poprawiłem moje przykłady.
BenAlabaster

Jeśli korzystasz z 2.0, nie masz też var.
rekurencyjny

1
Cóż, myślę, że jeśli chcesz być wybredny, zadeklaruję je poprawnie: P
BenAlabaster,

Jako osoba, która utknęła w używaniu .NET 2.0, została to bardzo doceniona! Sława.
kayleeFrye_onDeck

26

Cóż, nie określiłeś, której wersji .Net używasz.

Zakładając, że masz 3.5, innym sposobem jest metoda ElementAt:

var e = enumerable.ElementAt(0);

2
ElementAt (0), ładne i proste.
JMD


1

Spróbuj tego

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

0

Użyj FirstOrDefault lub pętli foreach, jak już wspomniano. Należy unikać ręcznego pobierania modułu wyliczającego i wywoływania Current. foreach pozbędzie się modułu wyliczającego za Ciebie, jeśli implementuje IDisposable. Podczas wywoływania MoveNext i Current musisz usunąć je ręcznie (jeśli to możliwe).


1
Jakie są dowody na to, że należy unikać wyliczającego? Testy wydajności na moim komputerze wskazują, że ma około 10% wzrost wydajności w stosunku do wszystkich.
BenAlabaster

To zależy od tego, co wyliczasz. Erumerator mógłby zamknąć połączenie z bazą danych, usunąć i zamknąć uchwyt pliku, zwolnić niektóre zablokowane obiekty itp. Wydaje mi się, że nie pozbycie się modułu wyliczającego jakąś listę liczb całkowitych nie będzie szkodliwe.
Mouk

0

Jeśli Twój IEnumerable nie ujawnia tego, <T>a Linq zawiedzie, możesz napisać metodę przy użyciu refleksji:

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;
}

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.