Как получить первый элемент из IEnumerable <T> в .net?


157

Я часто хочу получить первый элемент IEnumerable<T>в .net, и я не нашел хороший способ сделать это. Лучшее, что я придумал, это:

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

Тьфу! Так есть ли хороший способ сделать это?

Ответы:


242

Если вы можете использовать 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()все равно может вызвать исключение в случае, когда перечислимое содержит более одного элемента.

Редактировать: Спасибо Marc Gravell за указание, что мне нужно избавиться от моего IEnumeratorобъекта после его использования - я отредактировал не-LINQ пример, чтобы отобразить usingключевое слово для реализации этого шаблона.


2
Стоит отметить, что SingleOrDefault вернет 1) Единственный элемент, если есть только один элемент. 2) ноль, если нет элементов. 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, у вас тоже нет var.
— рекурсивный

1
Ну, я думаю, если ты хочешь стать привередливым, я
— объявлю

Как человек, который застрял с использованием .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, как уже упоминалось. Следует избегать ручной выборки перечислителя и вызова Current. foreach будет распоряжаться вашим перечислителем, если он реализует IDisposable. При вызове MoveNext и Current вы должны утилизировать его вручную (если применимо).


1
Какие есть доказательства того, что счетчика следует избегать? Тесты производительности на моей машине показывают, что она имеет примерно 10% прирост производительности по сравнению с foreach.
— BenAlabaster

Это зависит от того, что вы перечисляете. Erumerator может закрыть соединение с базой данных, очистить и закрыть дескриптор файла, освободить некоторые заблокированные объекты и т. Д. Не избавляться от перечислителя какого-либо целочисленного списка, я думаю, не будет вредно.
— Моук

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.