Як отримати перший елемент з IEnumerable <T> у .net?


156

Мені часто хочеться схопити перший елемент мережі IEnumerable<T>.net, і я не знайшов приємного способу це зробити. Я найкраще придумав:

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

Гидота! Отже, чи є приємний спосіб це зробити?

Відповіді:


241

Якщо ви можете використовувати LINQ, ви можете використовувати:

var e = enumerable.First();

Це викине виняток, хоча якщо номер порожній: у такому випадку ви можете використовувати:

var e = enumerable.FirstOrDefault();

FirstOrDefault()повернеться, default(T)якщо нумерація буде порожньою, що буде nullдля посилальних типів або за нульовим значенням для типів значень.

Якщо ви не можете використовувати LINQ, ваш підхід є технічно правильним і не відрізняється від створення перелічувача з використанням методів GetEnumeratorі MoveNextдля отримання першого результату (цей приклад передбачає, що численні є IEnumerable<Elem>):

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

Джоель Куехорн, згаданий .Single()у коментарях; це також спрацює, якщо ви очікуєте, що ваш номер містить точно один елемент - однак він викине виняток, якщо він порожній або більший, ніж один елемент. Існує відповідний SingleOrDefault()метод, який охоплює цей сценарій аналогічним чином FirstOrDefault(). Однак Девід Б пояснює, що SingleOrDefault()все-таки може бути винятком випадок у тому випадку, коли перелік містить більше одного елемента.

Редагувати: Дякую Марку Гравеллу за те, що він зазначив, що мені потрібно розпоряджатися своїм IEnumeratorоб’єктом після його використання - я відредагував приклад, який не є LINQ, щоб відобразити usingключове слово для реалізації цього шаблону.


2
Варто зазначити, що SingleOrDefault поверне 1) Єдиний елемент, якщо є лише один елемент. 2) null, якщо немає елементів. 3) киньте виняток, якщо є більше одного предмета.
Емі Б

Добрий дзвінок Девід Б - додав замітку.
Ерік Форбс

36

На випадок, якщо ви використовуєте .NET 2.0 і не маєте доступу до LINQ:

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

Це має робити те, що ви шукаєте ... він використовує дженерики, щоб ви отримали перший предмет будь-якого типу IEnumerable.

Назвіть це так:

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

Або

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

Ви можете легко змінити його, щоб імітувати метод розширення 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;
    }
} 

Називаючи це так:

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

Звичайно , якщо ви робите мати доступ до LINQ, тобто багато хороших відповідей вже відповідав ...


На жаль, ви праві, дякую. Я виправив свої приклади.
BenAlabaster

Якщо ви перебуваєте на версії 2.0, у вас також немає вару.
рекурсивна

1
Я гадаю, якщо ви хочете отримати вигадливість, я оголошу їх належним чином: P
BenAlabaster

Як хтось, хто застряг у використанні .NET 2.0, це було дуже вдячно! Кудос.
kayleeFrye_onDeck

26

Ну, ви не вказали, яку версію .Net використовуєте.

Якщо припустити, що у вас є 3.5, інший спосіб - метод ElementAt:

var e = enumerable.ElementAt(0);

2
ElementAt (0), приємний і простий.
JMD


1

спробуйте це

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

0

Використовуйте FirstOrDefault або цикл foreach, як уже згадувалося. Слід уникати ручного вибору перелічувача та виклику струму. foreach розпоряджається вашим перерахувачем, якщо він реалізує IDisposable. Під час виклику MoveNext і Current ви повинні розпоряджатися ним вручну (якщо це можливо).


1
Які існують докази того, що нумератор повинен уникати? Тести працездатності на моїй машині показують, що він має приблизно 10% -ний коефіцієнт підвищення продуктивності порівняно з foreach.
BenAlabaster

Це залежить від того, що ви перераховуєте. Erumerator міг би закрити з'єднання з базою даних, розминути і закрити обробку файлів, випустити деякі заблоковані об'єкти тощо. Якщо викинути нумерометр певного цілого списку, я не думаю, буде корисним.
Mouk

0

Якщо ваш IEnumerable не демонструє це, <T>а Linq виходить з ладу, ви можете написати метод, використовуючи відображення:

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.