ฉันจะรับองค์ประกอบแรกจาก IEnumerable <T> ใน. net ได้อย่างไร


157

ฉันมักจะต้องการที่จะคว้าองค์ประกอบแรกของIEnumerable<T>. สุทธิและฉันไม่ได้พบวิธีที่ดีที่จะทำ สิ่งที่ดีที่สุดที่ฉันคิดไว้คือ:

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

yuck! ดังนั้นมีวิธีที่ดีในการทำเช่นนี้?

คำตอบ:


242

หากคุณสามารถใช้ LINQ คุณสามารถใช้:

var e = enumerable.First();

สิ่งนี้จะทำให้เกิดข้อยกเว้นแม้ว่านับไม่ได้ว่าว่างเปล่า: ในกรณีนี้คุณสามารถใช้:

var e = enumerable.FirstOrDefault();

FirstOrDefault()จะกลับมาdefault(T)ถ้าจำนวนที่ว่างเปล่าซึ่งจะเป็นnullประเภทการอ้างอิงหรือเริ่มต้น 'zero-value' สำหรับประเภทค่า

หากคุณไม่สามารถใช้ LINQ ได้วิธีการของคุณจะถูกต้องทางเทคนิคและไม่แตกต่างจากการสร้างตัวแจงนับโดยใช้GetEnumeratorและMoveNextวิธีการเพื่อดึงผลลัพธ์แรก (ตัวอย่างนี้ถือว่าสมมติว่านับได้IEnumerable<Elem>):

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

Joel Coehoornพูดถึง.Single()ในความคิดเห็น; สิ่งนี้จะใช้งานได้หากคุณคาดหวังว่าจำนวนของคุณจะมีองค์ประกอบหนึ่งอย่างแน่นอน - อย่างไรก็ตามมันจะส่งข้อยกเว้นหากองค์ประกอบนั้นว่างเปล่าหรือใหญ่กว่าองค์ประกอบหนึ่ง มีความสอดคล้องกันคือวิธีการที่ครอบคลุมสถานการณ์นี้ในลักษณะคล้ายกับSingleOrDefault() FirstOrDefault()อย่างไรก็ตามDavid Bอธิบายว่าSingleOrDefault()อาจยังมีข้อยกเว้นในกรณีที่ตัวนับมีมากกว่าหนึ่งรายการ

แก้ไข: ขอบคุณMarc Gravell ที่ชี้ให้เห็นว่าฉันต้องกำจัดIEnumeratorวัตถุของฉันหลังจากใช้มัน - ฉันได้แก้ไขตัวอย่างที่ไม่ใช่ LINQ เพื่อแสดงusingคำหลักเพื่อใช้รูปแบบนี้


2
น่าจะชี้ให้เห็นว่า SingleOrDefault จะส่งคืน 1) รายการเดียวถ้ามีเพียงรายการเดียว 2) เป็นโมฆะหากไม่มีรายการ 3) ส่งข้อยกเว้นหากมีมากกว่าหนึ่งรายการ
Amy B

Good call David B - เพิ่มบันทึก
Erik Forbes

36

ในกรณีที่คุณใช้. NET 2.0 และไม่มีสิทธิ์เข้าถึง LINQ:

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

สิ่งนี้ควรทำในสิ่งที่คุณกำลังมองหา ... มันใช้ generics เพื่อให้คุณได้ไอเท็มแรกใน 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
ผมคิดว่าถ้าคุณอยากได้รับจู้จี้จุกจิกฉันจะประกาศให้ถูกต้อง: P
BenAlabaster

ในฐานะที่เป็นคนที่ติดกับ. NET 2.0 นี่เป็นที่นิยมอย่างมาก ความรุ่งโรจน์
kayleeFrye_onDeck

26

คุณไม่ได้ระบุว่าจะใช้. Net เวอร์ชันใด

สมมติว่าคุณมี 3.5 วิธีอื่นคือวิธี ElementAt:

var e = enumerable.ElementAt(0);

2
องค์ประกอบ (0) ดีและเรียบง่าย
JMD



0

ใช้ FirstOrDefault หรือ foreach loop ดังที่ได้กล่าวไปแล้ว การดึงตัวแจงนับและการเรียกกระแสด้วยตนเองควรหลีกเลี่ยง foreach จะกำจัดตัวแจงนับให้คุณหากใช้ IDisposable เมื่อเรียกใช้ MoveNext และ Current คุณจะต้องทำการกำจัดด้วยตัวเอง (ถ้า aplicable)


1
มีหลักฐานอะไรบ้างที่ผู้แจกแจงควรหลีกเลี่ยง? การทดสอบประสิทธิภาพของเครื่องของฉันระบุว่ามีประสิทธิภาพเพิ่มขึ้นประมาณ 10% เหนือกว่ารุ่นก่อน
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;
}

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.