Làm cách nào để có được phần tử đầu tiên từ <T> trong IE.


155

Tôi thường muốn lấy phần tử đầu tiên của IEnumerable<T>.net và tôi không tìm thấy cách nào hay để làm điều đó. Điều tốt nhất tôi nghĩ ra là:

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

Kinh quá! Vì vậy, có một cách tốt đẹp để làm điều này?

Câu trả lời:


240

Nếu bạn có thể sử dụng LINQ, bạn có thể sử dụng:

var e = enumerable.First();

Điều này sẽ đưa ra một ngoại lệ mặc dù nếu liệt kê là trống: trong trường hợp đó bạn có thể sử dụng:

var e = enumerable.FirstOrDefault();

FirstOrDefault()sẽ trả về default(T)nếu liệt kê là trống, sẽ nulldành cho các loại tham chiếu hoặc 'giá trị không' mặc định cho các loại giá trị.

Nếu bạn không thể sử dụng LINQ, thì cách tiếp cận của bạn là đúng về mặt kỹ thuật và không khác gì tạo ra một điều tra viên bằng cách sử dụng các phương thức GetEnumeratorMoveNextđể lấy kết quả đầu tiên (ví dụ này giả sử liệt kê là một IEnumerable<Elem>):

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

Joel Coehoorn đã đề cập .Single()trong các ý kiến; điều này cũng sẽ hoạt động, nếu bạn đang mong đợi vô số của mình chứa chính xác một yếu tố - tuy nhiên nó sẽ đưa ra một ngoại lệ nếu nó trống hoặc lớn hơn một yếu tố. Có một SingleOrDefault()phương pháp tương ứng bao trùm kịch bản này theo cách tương tự FirstOrDefault(). Tuy nhiên, David B giải thích rằng SingleOrDefault()vẫn có thể đưa ra một ngoại lệ trong trường hợp vô số chứa nhiều hơn một mục.

Chỉnh sửa: Cảm ơn Marc Gravell đã chỉ ra rằng tôi cần loại bỏ IEnumeratorđối tượng của mình sau khi sử dụng nó - Tôi đã chỉnh sửa ví dụ không phải LINQ để hiển thị usingtừ khóa để triển khai mẫu này.


2
Đáng để chỉ ra rằng SingleOrDefault sẽ trả về 1) Mục duy nhất nếu chỉ có một mục. 2) null nếu không có mục. 3) ném ngoại lệ nếu có nhiều hơn một mục.
Amy B

Cuộc gọi tốt David B - thêm một ghi chú.
Erik Forbes

36

Chỉ trong trường hợp bạn đang sử dụng .NET 2.0 và không có quyền truy cập vào LINQ:

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

Điều này sẽ làm những gì bạn đang tìm kiếm ... nó sử dụng thuốc generic để bạn có được mục đầu tiên trên bất kỳ loại IEnumerable nào.

Gọi nó như vậy:

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

Hoặc là

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

Bạn có thể sửa đổi nó đủ dễ dàng để bắt chước phương thức mở rộng IEnumerable.EuityAt () của .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;
    }
} 

Gọi nó như vậy:

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

Tất nhiên nếu bạn làm được tiếp cận với LINQ, sau đó có rất nhiều câu trả lời tốt posted đã ...


Rất tiếc, tất nhiên là bạn đúng, cảm ơn. Tôi đã sửa các ví dụ của mình.
BenAlabaster

Nếu bạn đang ở trên 2.0, bạn cũng không có var.
đệ quy

1
Chà tôi đoán nếu bạn muốn nhận được sự tinh tế, tôi sẽ khai báo chúng đúng cách: P
BenAlabaster

Là một người bị mắc kẹt khi sử dụng .NET 2.0, điều này đã được đánh giá rất cao! Thanh danh.
kayleeFrye_onDeck

26

Chà, bạn đã không chỉ định phiên bản .Net nào bạn đang sử dụng.

Giả sử bạn có 3,5, một cách khác là phương thức ElementAt:

var e = enumerable.ElementAt(0);

2
ElementAt (0), đẹp và đơn giản.
JMD


1

thử cái này

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

0

Sử dụng FirstOrDefault hoặc vòng lặp foreach như đã đề cập. Nên tránh tìm nạp thủ công và gọi Hiện tại. foreach sẽ loại bỏ điều tra viên của bạn cho bạn nếu nó thực hiện IDis Dùng một lần. Khi gọi MoveNext và Hiện tại, bạn phải loại bỏ nó theo cách thủ công (nếu có thể áp dụng).


1
Bằng chứng nào cho thấy điều tra viên cần tránh? Các bài kiểm tra hiệu suất trên máy của tôi cho thấy rằng nó có mức tăng hiệu suất xấp xỉ 10% so với foreach.
BenAlabaster

Nó phụ thuộc vào những gì bạn đang liệt kê. Một Erumerator có thể đóng kết nối với Cơ sở dữ liệu, xử lý và đóng tệp xử lý, giải phóng một số đối tượng bị khóa, v.v. Không loại bỏ một liệt kê của một số danh sách số nguyên sẽ không có hại, tôi đoán vậy.
Mouk

0

Nếu IEnumerable của bạn không phơi bày nó <T>và Linq thất bại, bạn có thể viết một phương thức bằng cách sử dụng sự phản chiếu:

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

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.