Bagaimana cara saya mendapatkan elemen pertama dari IEnumerable <T> di .net?

157

Saya sering ingin mengambil elemen pertama dari IEnumerable<T>.net, dan saya belum menemukan cara yang bagus untuk melakukannya. Yang terbaik yang saya hasilkan adalah:

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

Huek! Jadi, adakah cara yang bagus untuk melakukan ini?

TIMK
sumber

Jawaban:

242

Jika Anda dapat menggunakan LINQ Anda dapat menggunakan:

var e = enumerable.First();

Ini akan menghasilkan pengecualian meskipun jika enumerable kosong: dalam hal ini Anda dapat menggunakan:

var e = enumerable.FirstOrDefault();

FirstOrDefault()akan kembali default(T)jika enumerable kosong, yang akan nulluntuk tipe referensi atau 'nilai nol' default untuk tipe nilai.

Jika Anda tidak dapat menggunakan LINQ, maka pendekatan Anda secara teknis benar dan tidak berbeda dengan membuat enumerator menggunakan metode GetEnumeratordan MoveNextuntuk mengambil hasil pertama (contoh ini menganggap enumerable adalah:) IEnumerable<Elem>:

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

Joel Coehoorn disebutkan .Single()dalam komentar; ini juga akan berfungsi, jika Anda mengharapkan enumerable Anda mengandung tepat satu elemen - namun itu akan menimbulkan pengecualian jika elemen tersebut kosong atau lebih besar dari satu elemen. Ada SingleOrDefault()metode yang sesuai yang mencakup skenario ini dengan cara yang mirip dengan FirstOrDefault(). Namun, David B menjelaskan bahwa SingleOrDefault()mungkin masih ada pengecualian dalam kasus di mana enumerable berisi lebih dari satu item.

Sunting: Terima kasih Marc Gravell untuk menunjukkan bahwa saya harus membuang IEnumeratorobjek saya setelah menggunakannya - Saya telah mengedit contoh non-LINQ untuk menampilkan usingkata kunci untuk menerapkan pola ini.

Erik Forbes
sumber
2
Layak menunjukkan bahwa SingleOrDefault akan kembali 1) Satu-satunya item jika hanya ada satu item. 2) null jika tidak ada item. 3) melemparkan pengecualian jika ada lebih dari satu item.
Amy B
Panggilan bagus David B - menambahkan catatan.
Erik Forbes
36

Hanya dalam kasus Anda menggunakan .NET 2.0 dan tidak memiliki akses ke LINQ:

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

Ini harus melakukan apa yang Anda cari ... menggunakan generik sehingga Anda mendapatkan item pertama pada semua jenis IEnumerable.

Sebut seperti ini:

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

Atau

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

Anda dapat memodifikasinya dengan mudah untuk meniru IEnumerable.ElementAt () metode ekstensi .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;
    }
} 

Menyebutnya seperti ini:

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

Tentu saja jika Anda lakukan memiliki akses ke LINQ, maka ada banyak jawaban yang baik diposting sudah ...

BenAlabaster
sumber
Ups, tentu saja Anda benar, terima kasih. Saya telah memperbaiki contoh saya.
BenAlabaster
Jika Anda menggunakan 2.0, Anda tidak memiliki var.
Rekursif
1
Baik saya kira jika Anda ingin mendapatkan rewel saya akan menyatakan mereka benar: P
BenAlabaster
Sebagai seseorang yang terjebak menggunakan .NET 2.0, ini sangat dihargai! Pujian.
kayleeFrye_onDeck
26

Yah, Anda tidak menentukan versi .Net yang Anda gunakan.

Dengan asumsi Anda memiliki 3,5, cara lain adalah metode ElementAt:

var e = enumerable.ElementAt(0);
Adam Lassek
sumber
2
ElementAt (0), bagus dan sederhana.
JMD
6

FirstOrDefault ?

Elem e = enumerable.FirstOrDefault();
//do something with e
Amy B
sumber
1

coba ini

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

Gunakan FirstOrDefault atau loop foreach seperti yang telah disebutkan. Pengambilan enumerator secara manual dan memanggil Arus harus dihindari. foreach akan membuang enumerator Anda untuk Anda jika mengimplementasikan IDisposable. Saat memanggil MoveNext dan Lancar Anda harus membuangnya secara manual (jika dapat diterapkan).

Mouk
sumber
1
Apa buktinya bahwa pencacah harus dihindari? Tes kinerja pada mesin saya menunjukkan bahwa ia memiliki perkiraan kenaikan kinerja 10% lebih awal.
BenAlabaster
Itu tergantung pada apa yang Anda hitung. Seorang Erumerator dapat menutup koneksi ke Database, menangani file fush dan close, melepaskan beberapa objek yang terkunci, dll. Tidak membuang enumerator dari beberapa daftar integer tidak akan berbahaya, saya kira.
Mouk
0

Jika IEnumerable Anda tidak mengeksposnya <T>dan Linq gagal, Anda dapat menulis metode menggunakan refleksi:

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
sumber
0

Anda juga dapat mencoba versi yang lebih umum yang memberi Anda elemen engan

enumerable.ElementAtOrDefault (i));

semoga membantu

orang acak di internet
sumber