Como obtenho o primeiro elemento de um IEnumerable <T> no .net?


157

Muitas vezes, quero pegar o primeiro elemento de um IEnumerable<T>.net e não encontrei uma boa maneira de fazê-lo. O melhor que eu criei é:

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

Que nojo! Então, existe uma boa maneira de fazer isso?

Respostas:


242

Se você pode usar o LINQ, pode usar:

var e = enumerable.First();

Isso gerará uma exceção, se enumerable estiver vazio: nesse caso, você poderá usar:

var e = enumerable.FirstOrDefault();

FirstOrDefault()retornará default(T)se o enumerável estiver vazio, que será nullpara tipos de referência ou o 'valor zero' padrão para tipos de valor.

Se você não pode usar o LINQ, sua abordagem é tecnicamente correta e não é diferente de criar um enumerador usando os métodos GetEnumeratore MoveNextpara recuperar o primeiro resultado (este exemplo assume que enumerável é um IEnumerable<Elem>):

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

Joel Coehoorn mencionado .Single()nos comentários; isso também funcionará, se você espera que o seu enumerável contenha exatamente um elemento - no entanto, lançará uma exceção se estiver vazio ou maior que um elemento. Existe um SingleOrDefault()método correspondente que cobre esse cenário de maneira semelhante a FirstOrDefault(). No entanto, David B explica que SingleOrDefault()ainda pode lançar uma exceção no caso em que o enumerável contém mais de um item.

Edit: Obrigado Marc Gravell por apontar que preciso descartar meu IEnumeratorobjeto depois de usá-lo - editei o exemplo não-LINQ para exibir a usingpalavra-chave para implementar esse padrão.


2
Vale ressaltar que SingleOrDefault retornará 1) O único item se houver apenas um item. 2) nulo se não houver itens. 3) lance uma exceção se houver mais de um item.
Amy B

Good call David B - adicionou uma nota.
Erik Forbes

36

Caso você esteja usando o .NET 2.0 e não tenha acesso ao LINQ:

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

Isso deve fazer o que você está procurando ... ele usa genéricos para obter o primeiro item em qualquer tipo IEnumerable.

Chame assim:

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

Ou

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

Você pode modificá-lo com prontidão suficiente para imitar o método de extensão IEnumerable.ElementAt () do .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;
    }
} 

Chamando assim:

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

Claro, se você fazer têm acesso a LINQ, então há uma abundância de boas respostas já postou ...


Opa, é claro que você está certo, obrigado. Corrigi meus exemplos.
BenAlabaster

Se você está no 2.0, também não tem var.
recursiva

1
Bem, eu acho que se você quer ficar enjoado Eu vou declará-los corretamente: P
BenAlabaster

Como alguém que está preso usando o .NET 2.0, isso foi muito apreciado! Parabéns.
precisa saber é o seguinte

26

Bem, você não especificou qual versão do .Net você está usando.

Supondo que você tenha 3,5, outra maneira é o método ElementAt:

var e = enumerable.ElementAt(0);

2
ElementAt (0), agradável e simples.
JMD


1

tente isso

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

0

Use FirstOrDefault ou um loop foreach como já mencionado. Buscar manualmente um enumerador e chamar Current deve ser evitado. O foreach descartará seu enumerador para você se implementar IDisposable. Ao chamar MoveNext e Current, você deve descartá-lo manualmente (se aplicável).


1
Que evidência existe de que o enumerador deve ser evitado? Os testes de desempenho em minha máquina indicam que ele tem um ganho de desempenho aproximado de 10% em relação ao foreach.
BenAlabaster

Depende do que você está enumerando. Um Erumerator pode fechar a conexão com o Banco de Dados, fush e fechar o manipulador de arquivos, liberar alguns objetos bloqueados, etc. Não descartar um enumerador de alguma lista inteira não será prejudicial, eu acho.
Mouk

0

Se seu IEnumerable não o <T>expuser e o Linq falhar, você poderá escrever um método usando a reflexão:

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

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.