Sự khác biệt giữa IEnumerable và Array, IList và List là gì?


91

Sự khác biệt giữa IEnumerablevà là Arraygì?

Sự khác biệt giữa IListvà là Listgì?

Chúng dường như có cùng một chức năng.

Câu trả lời:


101

IEnumerable chỉ cung cấp chức năng "có thể lặp lại" tối thiểu. Bạn có thể duyệt qua trình tự, nhưng đó là về nó. Điều này có nhược điểm - ví dụ, rất kém hiệu quả khi đếm các phần tử bằng IEnumerable, hoặc để lấy phần tử thứ n - nhưng nó cũng có ưu điểm - ví dụ, IEnumerable có thể là một dãy vô tận, giống như dãy các số nguyên tố.

Mảng là một tập hợp có kích thước cố định với quyền truy cập ngẫu nhiên (tức là bạn có thể lập chỉ mục vào nó).

Danh sách là một tập hợp có kích thước thay đổi (tức là bạn có thể thêm và bớt các phần tử) với quyền truy cập ngẫu nhiên.

IList là một giao diện tóm tắt chức năng danh sách (đếm, thêm, xóa, truy cập trình lập chỉ mục) khỏi các lớp cụ thể khác nhau như List, BindingList, ObservableCollection, v.v.


2
Theo câu trả lời được chấp nhận ở đây stackoverflow.com/questions/1826658/… . Đếm trên IEnumerable là gần như giống nhau beacuse nó cố gắng gọi Đếm trên ICollection
— Karsten

15
Có và không. Phương thức mở rộng Count () kiểm tra xem IEnumerable có phải là ICollection hay không và gọi thuộc tính Count của nó nếu có. Nếu IEnumerable không phải là ICollection thì Count () sẽ quay trở lại việc lặp lại chuỗi. Có lẽ tôi nên nói rằng "rất kém hiệu quả nếu chỉ đếm các phần tử chỉ sử dụng IEnumerable", vì tất nhiên sự hiện diện của IEnumerable không ngăn bạn sử dụng một phương pháp hiệu quả hơn nếu đối tượng có một phương thức mở rộng LINQ to Objects chính xác cái đó.
— itowlson

17

IEnumerable là một giao diện cho phép lặp lại thông qua một tập hợp các mục (ví dụ thông qua từ khóa foreach).

Mảng là một bản chất .NET. Nó chứa các vật phẩm cùng loại, nhưng có kích thước cố định. Khi bạn tạo một mảng với x phần tử, nó không thể phát triển hoặc thu nhỏ.

IList định nghĩa giao diện cho một danh sách và cũng triển khai IEnumerable.

Danh sách thực hiện giao diện IList; nó là một loại danh sách cụ thể.

Sự khác biệt giữa Danh sách .NET và mảng là danh sách có thể có các phần tử được thêm vào chúng - chúng phát triển đủ lớn để chứa tất cả các mục được yêu cầu. Danh sách này lưu trữ nội bộ này trong một mảng và khi mảng không còn đủ lớn để chứa tất cả các phần tử, một mảng mới sẽ được tạo và các mục được sao chép qua.

IList & mảng đều triển khai IEnumerable. Đó là cách các giao diện hoạt động - các lớp thực hiện hợp đồng và hoạt động theo một kiểu tương tự và kết quả là có thể được xử lý tương tự (bạn biết rằng lớp triển khai IEnumerable, bạn không cần biết cách thực hiện hoặc lý do). Tôi khuyên bạn nên đọc về các giao diện, v.v.


1
Thêm nhận xét này cho những độc giả không đọc tất cả các câu trả lời: mảng và Danh sách <> đều triển khai IList <> (các mảng triển khai phần lớn nó một cách rõ ràng, có lẽ là do nhiều thành viên ném NotSupportedException). Cũng đúng hơn khi nói rằng IList kế thừa từ IEnumerable hơn là nói rằng nó thực hiện nó.
— phoog

9

IEnumerable và IList là các giao diện . Mảng và Danh sách là các lớp. Mảng thực hiện IEnumerable. Danh sách thực hiện IList mở rộng IEnumerable.

Chỉnh sửa: như itowlson đã đề cập trong một bình luận, Array cũng thực hiện IList.


Tôi cũng không! Tuy nhiên, điều gì sẽ xảy ra nếu bạn bắt đầu gọi IList.RemoveAt trên một mảng?
— Mark Simpson

Nó ném ra một NotSupportedException. (Lưu ý IList.RemoveAt và phương pháp IList tương tự được thực hiện một cách rõ ràng về mảng để họ không hiển thị trên một tham chiếu mảng bình thường!)
— itowlson

1
@itowlson Tôi sẽ chỉ ra rằng chỉ System.Array (mỗi lớp) thực hiện IList, nhưng T [] không triển khai IList <T>. T [] triển khai IEnumerable <T> để thay thế. Tôi nghĩ OP không nhất thiết phải yêu cầu System.Array khi yêu cầu IMHO "Array"
— usr-local-ΕΨΗΕΛΩΝ

6

Thế hệ của một bộ sưu tập IEnumerable là lười biếng. Thí dụ:

public IEnumerable<int> GetTwoInts()
{
  yield return 1;
  yield return 2;
}
public void Something()
{
  var twoInts = GetTwoInts();
}

Trong phương thức Something, lệnh gọi tới GetTwoInts () sẽ không thực sự dẫn đến phương thức GetTwoInts được thực thi vì phép liệt kê không bao giờ được lặp lại.


Bạn có thể vui lòng giải thích nó một chút? Tôi không chắc liệu mình có hiểu đúng không. Bạn có thể hiển thị một dòng mã sau đó sẽ thực sự dẫn đến việc phương thức đó được thực thi không? Cảm ơn!
— paaone,

@paaone đặt twoIntsmột foreachhoặc cuộc gọi twoInts.ToList().
— row1

2

IEnumerablelà một giao diện có mục đích chung được sử dụng bởi nhiều lớp, chẳng hạn như Array, Listvà Stringđể cho phép ai đó lặp qua một tập hợp. Về cơ bản, nó là thứ thúc đẩy foreachtuyên bố.

IListthường là cách bạn hiển thị các biến loại Listcho người dùng cuối. Giao diện này cho phép truy cập ngẫu nhiên vào bộ sưu tập cơ bản.


2

Để bổ sung cho các câu trả lời khác, hãy lưu ý rằng có sự khác biệt về hiệu suất giữa IList<T>và List<T>khi thực hiện câu lệnh foreach.

Đó là bởi vì đối tượng trình vòng lặp được trả về List<T>.GetEnumeratorlà kiểu giá trị trong khi đối tượng được trả về là kiểu IList<T>.GetEnumeratortham chiếu và do đó yêu cầu cấp phát bộ nhớ (xem Liệt kê kiểu giá trị của danh sách trong c # ).

Theo tôi, IList<T>không phải là một giao diện rất tốt. Ví dụ gọi Addcó thể ném (xem Tại sao mảng thực hiện IList? ). Nếu bạn cần đóng gói, bạn nên sử dụng IEnumerable<T>hoặc IReadOnlyList<T>.


1

Ngoài các câu trả lời khác, hiểu được sự khác biệt giữa Enumerable so với List / Array khi sử dụng LINQ có thể có tác động rất lớn đến hiệu suất. Tóm lại, Enumerable có thể được coi như một trình tạo truy vấn trong khi Danh sách / Mảng là kết quả của truy vấn.

Trong ngữ cảnh của LINQ to SQL sử dụng EntityFramework, phần trước chỉ là xây dựng truy vấn SQL mà không thực thi nó đối với cơ sở dữ liệu cũng như tải bất kỳ dữ liệu nào vào bộ nhớ trong khi phần sau thì ngược lại. Đó là lý do tại sao chúng tôi sẽ hoãn cuộc gọi .ToList()cho đến khi chúng tôi cần nó trong bộ nhớ để thực hiện logic nghiệp vụ.

Trong ngữ cảnh khác, biểu thức LINQ trả về IEnumerable sẽ trì hoãn việc thực thi cho đến khi .ToList()được gọi. Hãy xem xét ví dụ dưới đây:

void Main()
{
    var w1 = "AB".AsEnumerable();
    Console.WriteLine($"W1: 1");
    w1 = w1.Where(W1);
    Console.WriteLine($"W1: 2");
    w1 = w1.Where(W2);
    Console.WriteLine($"W1: 3");
    w1.ToList();    
    Console.WriteLine($"----------");

    var w2 = "CD".AsEnumerable();
    Console.WriteLine($"W2: 1");
    w2 = w2.Where(W1);
    Console.WriteLine($"W2: 2");
    w2 = w2.ToList();
    Console.WriteLine($"W2: 3");
    w2 = w2.Where(W2);
    Console.WriteLine($"W2: 4");
    w2.ToList();    
    Console.WriteLine($"----------");
}

bool W1(char arg)
{
    Console.WriteLine($"W1:{arg}");
    return true;
}

bool W2(char arg)
{
    Console.WriteLine($"W2:{arg}");
    return true;
}

OUTPUT:
W1: 1
W1: 2
W1: 3
W1:A
W2:A
W1:B
W2:B
----------
W2: 1
W2: 2
W1:C
W1:D
W2: 3
W2: 4
W2:C
W2:D
----------

Trong ví dụ đầu tiên, hai .Where() đang được "nối" và được thực thi cùng nhau ở cuối khi .ToList()được gọi với mục " A " đi qua đường ống trước rồi đến mục " B ", do đó nhìn thấy " AABB " trong đầu ra, trong khi ở ví dụ thứ hai, những .Where()được thực hiện mỗi khi nếu chúng ta gọi là .ToList()ngay lập tức sau đó, vì thế nhìn thấy " CD " và sau đó " CD " một lần nữa, sản lượng gấp đôi. Do đó, mỗi khi Enumerable được chuyển đổi thành Danh sách hoặc Mảng sẽ tốn một O (n) lần lặp trên tất cả các mục trong bộ sưu tập, điều này sẽ ảnh hưởng đến hiệu suất khi bộ sưu tập lớn.

Mặc dù chúng ta không có xu hướng viết mã theo cách này để gọi .ToList()giữa các cuộc gọi LINQ nhưng nó sẽ xảy ra thường xuyên hơn khi chúng ta tính toán mã vào các phương thức có thể tái sử dụng mà trả về List/Arraythay vì IEnumerable.

Tuy nhiên, điều này không có nghĩa là chúng ta nên luôn vận hành IEnumerable . Như đã đề cập bởi towlson , các hoạt động như .Count()sẽ gây ra sự lặp lại trên bộ sưu tập trong khi Danh sách hoặc Mảng sẽ có thông tin này được tính toán trước và do đó việc chuyển đổi thành List/Arraysẽ hiệu quả hơn nếu bạn định gọi .Count()nhiều lần. Đây cũng là lý do tại sao có một thuộc .Counttính trên Danh sách hơn là một .Count()phương thức để đếm nó.


0

Đây là một bài viết cũ, nhưng vẫn nghĩ rằng sẽ trả lời. IEnumerable là một hành vi trong khi Mảng là một cấu trúc dữ liệu (Tập hợp các phần tử liền kề với kích thước cố định, tạo điều kiện thuận lợi cho việc truy cập các phần tử bằng các chỉ mục). Khi một Mảng triển khai IEnumerable, nó cũng phải mô tả thuộc tính vốn có của IEnumerable (tạo điều kiện thuận lợi cho việc lặp qua tập hợp).

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 cookie và Chính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.