Khi nào nên sử dụng IList và khi nào nên sử dụng Danh sách


180

Tôi biết rằng IList là giao diện và Danh sách là loại cụ thể nhưng tôi vẫn không biết khi nào nên sử dụng từng giao diện. Những gì tôi đang làm bây giờ là nếu tôi không cần các phương thức Sắp xếp hoặc Tìm tất cả tôi sử dụng giao diện. Tôi có đúng không Có cách nào tốt hơn để quyết định khi nào nên sử dụng giao diện hoặc loại cụ thể?


1
Nếu bất cứ ai vẫn còn thắc mắc, tôi tìm thấy câu trả lời tốt nhất ở đây: stackoverflow.com/questions/400135/listt-or-ilistt
Crismogram

Câu trả lời:


175

Có hai quy tắc tôi tuân theo:

  • Chấp nhận loại cơ bản nhất sẽ hoạt động
  • Trả về loại giàu nhất mà người dùng của bạn sẽ cần

Vì vậy, khi viết một hàm hoặc phương thức lấy một bộ sưu tập, hãy viết nó không lấy Danh sách, mà là IList <T>, ICollection <T> hoặc IEnumerable <T>. Các giao diện chung vẫn sẽ hoạt động ngay cả đối với các danh sách không đồng nhất vì System.Object cũng có thể là một T. Làm điều này sẽ giúp bạn đỡ đau đầu nếu bạn quyết định sử dụng Stack hoặc một số cấu trúc dữ liệu khác ở xa hơn. Nếu tất cả những gì bạn cần làm trong hàm là tìm kiếm thông qua nó, thì IEnumerable <T> thực sự là tất cả những gì bạn nên yêu cầu.

Mặt khác, khi trả lại một đối tượng ra khỏi hàm, bạn muốn cung cấp cho người dùng tập hợp hoạt động phong phú nhất có thể mà không cần phải thực hiện xung quanh. Vì vậy, trong trường hợp đó, nếu đó là Danh sách <T> bên trong, hãy trả lại một bản sao dưới dạng Danh sách <T>.


43
Bạn không nên đối xử với các loại đầu vào / đầu ra khác nhau. Cả hai loại đầu vào và đầu ra phải là loại cơ bản nhất (tốt nhất là giao diện) sẽ hỗ trợ nhu cầu của khách hàng. Đóng gói dựa vào việc nói với khách hàng càng ít về việc triển khai lớp của bạn càng tốt. Nếu bạn trả về một Danh sách cụ thể, thì bạn không thể thay đổi thành một loại tốt hơn khác mà không buộc tất cả khách hàng của bạn phải biên dịch lại / cập nhật.
Tro

11
Tôi không đồng ý với 2 quy tắc ... Tôi sẽ sử dụng loại nguyên thủy và đặc biệt nhất khi trở lại trong trường hợp này IList (IEnumarable tốt hơn) và bạn nên làm việc với List trong chức năng của mình bên trong. Sau đó, khi bạn cần "thêm" hoặc "sắp xếp" thì hãy sử dụng Bộ sưu tập nếu cần thêm thì sử dụng Danh sách. Vì vậy, quy tắc cứng của tôi sẽ là: BẮT ĐẦU luôn với IENumarable và nếu bạn cần nhiều hơn thì hãy gia hạn ...
ethem

2
Để thuận tiện cho bạn, "hai quy tắc" có một tên: nguyên tắc mạnh mẽ (còn gọi là luật của Postel) .
Easoncxz

Bất kể bên nào tranh luận về việc ai đó sẽ trả về loại cơ bản nhất hoặc loại phong phú nhất, điều cần xem xét là khi trả về một giao diện rất đơn giản, mã tiêu thụ có thể đôi khi - mặc dù không phải lúc nào - sử dụng một if...elsechuỗi với istừ khóa để hình ra một loại phong phú hơn nhiều cho nó và cuối cùng sử dụng nó và sử dụng nó. Vì vậy, bạn không nhất thiết phải đảm bảo che giấu bất cứ điều gì bằng cách sử dụng giao diện cơ bản, thay vì chỉ che khuất nó. Tuy nhiên, làm cho nó khó hơn cũng có thể khiến người viết mã tiêu dùng suy nghĩ kỹ về cách họ sử dụng nó.
Panzercrisis

6
Tôi rất không đồng ý về Điểm số 2, đặc biệt nếu đây là ở ranh giới dịch vụ / api. Trả lại các bộ sưu tập có thể sửa đổi có thể tạo ấn tượng rằng các bộ sưu tập là "trực tiếp" và các phương thức gọi như thế Add()Remove()có thể có hiệu ứng vượt ra ngoài bộ sưu tập. Trả về một giao diện chỉ đọc như IEnumerablethường là cách để sử dụng các phương thức truy xuất dữ liệu. Người tiêu dùng của bạn có thể chiếu nó thành một loại phong phú hơn khi cần thiết.
STW

56

Các nguyên tắc của Microsoft khi được FxCop kiểm tra không khuyến khích sử dụng Danh sách <T> trong các API công khai - thích IList <T>.

Ngẫu nhiên, bây giờ tôi hầu như luôn luôn khai báo các mảng một chiều là IList <T>, có nghĩa là tôi luôn có thể sử dụng thuộc tính <T> .Count của IList thay vì Array.Lạng. Ví dụ:

public interface IMyApi
{
    IList<int> GetReadOnlyValues();
}

public class MyApiImplementation : IMyApi
{
    public IList<int> GetReadOnlyValues()
    {
        List<int> myList = new List<int>();
        ... populate list
        return myList.AsReadOnly();
    }
}
public class MyMockApiImplementationForUnitTests : IMyApi
{
    public IList<int> GetReadOnlyValues()
    {
        IList<int> testValues = new int[] { 1, 2, 3 };
        return testValues;
    }
}

3
Tôi thích lời giải thích này / ví dụ nhất!
JonH

28

Có một điều quan trọng mà mọi người dường như luôn bỏ qua:

Bạn có thể truyền một mảng đơn giản cho một cái gì đó chấp nhận một IList<T>tham số, sau đó bạn có thể gọi IList.Add()và sẽ nhận được một ngoại lệ thời gian chạy:

Unhandled Exception: System.NotSupportedException: Collection was of a fixed size.

Ví dụ, hãy xem xét mã sau đây:

private void test(IList<int> list)
{
    list.Add(1);
}

Nếu bạn gọi như sau, bạn sẽ nhận được một ngoại lệ thời gian chạy:

int[] array = new int[0];
test(array);

Điều này xảy ra bởi vì sử dụng mảng đơn giản với IList<T>vi phạm nguyên tắc thay thế Liskov.

Vì lý do này, nếu bạn đang gọi, IList<T>.Add()bạn có thể muốn xem xét yêu cầu List<T>thay vì một IList<T>.


Điều này là đúng cho mọi giao diện. Nếu bạn muốn theo dõi thông qua lập luận của mình, bạn có thể lập luận rằng không bao giờ sử dụng bất kỳ giao diện nào, bởi vì một số triển khai của nó có thể ném. Mặt khác, nếu bạn xem xét đề xuất do OP đưa ra để thích List<T>hơn IList<T>, bạn cũng nên biết lý do tại sao IList<T>được đề xuất. (Ví dụ blog.msdn.microsoft.com/kcwalina/2005/09/26/ trên )
Micha Wiedenmann

3
@MichaWiedenmann Câu trả lời của tôi ở đây là cụ thể khi bạn gọi điện IList<T>.Add(). Tôi không nói rằng bạn không nên sử dụng IList<T>- Tôi chỉ đang chỉ ra một cạm bẫy có thể xảy ra. (Tôi có xu hướng sử dụng IEnumerable<T>hoặc IReadOnlyList<T>hoặc IReadOnlyCollection<T>ưu tiên để IList<T>nếu tôi có thể.)
Matthew Watson

24

Tôi đồng ý với lời khuyên của Lee về việc lấy tham số, nhưng không trả lại.

Nếu bạn chỉ định các phương thức của mình để trả về một giao diện có nghĩa là bạn có thể tự do thay đổi việc triển khai chính xác sau này mà không cần biết phương thức tiêu thụ nào. Tôi nghĩ rằng tôi không bao giờ cần phải thay đổi từ Danh sách <T> nhưng sau đó phải thay đổi để sử dụng thư viện danh sách tùy chỉnh cho chức năng bổ sung mà nó cung cấp. Bởi vì tôi chỉ trả lại một IList <T> không ai trong số những người sử dụng thư viện phải thay đổi mã của họ.

Tất nhiên chỉ cần áp dụng cho các phương thức có thể nhìn thấy bên ngoài (tức là các phương thức công khai). Cá nhân tôi sử dụng các giao diện ngay cả trong mã nội bộ, nhưng vì bạn có thể tự thay đổi tất cả mã nếu bạn thực hiện các thay đổi, điều đó không thực sự cần thiết.


22

Rất nhiều
Bạn nên thử và sử dụng loại ít cụ thể nhất phù hợp với mục đích của bạn.
IEnumerablelà ít cụ thể hơn IList.
Bạn sử dụng IEnumerablekhi bạn muốn lặp qua các mục trong bộ sưu tập.

IList
IList thực hiện IEnumerable.
Bạn nên sử dụng IListkhi bạn cần truy cập theo chỉ mục vào bộ sưu tập của mình, thêm và xóa các yếu tố, v.v ...

Danh sách
List thực hiện IList.


3
Tuyệt vời, câu trả lời rõ ràng, mà tôi đánh dấu là hữu ích. Tuy nhiên, tôi sẽ nói thêm rằng đối với hầu hết các nhà phát triển, hầu hết thời gian, sự khác biệt nhỏ về kích thước và hiệu suất chương trình không đáng lo ngại: nếu nghi ngờ, chỉ cần sử dụng Danh sách.
Graham Laight

9

Luôn luôn tốt nhất để sử dụng loại cơ sở thấp nhất có thể. Điều này mang lại cho người thực hiện giao diện của bạn hoặc người tiêu dùng phương pháp của bạn, cơ hội sử dụng bất cứ thứ gì họ thích đằng sau hậu trường.

Đối với các bộ sưu tập, bạn nên đặt mục tiêu sử dụng IEnumerable nếu có thể. Điều này mang lại sự linh hoạt nhất nhưng không phải lúc nào cũng phù hợp.


1
Luôn luôn là tốt nhất để chấp nhận loại cơ sở thấp nhất có thể. Trở về là một câu chuyện khác. Chọn những tùy chọn có khả năng hữu ích. Vì vậy, bạn nghĩ rằng khách hàng của bạn có thể muốn sử dụng truy cập được lập chỉ mục? Giữ họ khỏi - ToList()trả lại của bạn IEnumerable<T>đã là một danh sách, và trả lại một IList<T>thay thế. Bây giờ, khách hàng có thể hưởng lợi từ những gì bạn có thể cung cấp mà không cần nỗ lực.
Timo

5

Nếu bạn đang làm việc trong một phương thức duy nhất (hoặc thậm chí trong một lớp hoặc tập hợp trong một số trường hợp) và không ai ở bên ngoài sẽ thấy những gì bạn đang làm, hãy sử dụng tính đầy đủ của Danh sách. Nhưng nếu bạn đang tương tác với mã bên ngoài, như khi bạn trả về một danh sách từ một phương thức, thì bạn chỉ muốn khai báo giao diện mà không nhất thiết phải tự buộc mình vào một triển khai cụ thể, đặc biệt là nếu bạn không kiểm soát được ai biên dịch lại với bạn mã sau đó. Nếu bạn bắt đầu với một loại cụ thể và bạn quyết định đổi sang loại khác, ngay cả khi nó sử dụng cùng một giao diện, bạn sẽ phá vỡ mã của người khác trừ khi bạn bắt đầu với một loại cơ sở trừu tượng hoặc giao diện.


4

Tôi không nghĩ rằng có những quy tắc cứng và nhanh cho loại điều này, nhưng tôi thường tuân theo hướng dẫn sử dụng cách nhẹ nhất có thể cho đến khi thật cần thiết.

Ví dụ: giả sử bạn có một Personlớp và một Grouplớp. Một Groupví dụ có nhiều người, vì vậy một Danh sách ở đây sẽ có ý nghĩa. Khi tôi khai báo đối tượng danh sách trong Grouptôi sẽ sử dụng một IList<Person>và khởi tạo nó dưới dạng a List.

public class Group {
  private IList<Person> people;

  public Group() {
    this.people = new List<Person>();
  }
}

Và, nếu bạn thậm chí không cần mọi thứ trong IListbạn, bạn luôn có thể sử dụng IEnumerable. Với các trình biên dịch và bộ xử lý hiện đại, tôi không nghĩ thực sự có sự khác biệt về tốc độ, vì vậy đây không chỉ là vấn đề về kiểu dáng.


3
Tại sao không làm cho nó chỉ là một Danh sách ở nơi đầu tiên? Tôi vẫn không hiểu tại sao phần thưởng bạn nhận được từ việc biến nó thành IList sau đó trong hàm tạo bạn đưa nó vào Danh sách <>
chobo2

Tôi đồng ý, nếu bạn rõ ràng đang tạo một đối tượng Danh sách <T> thì bạn sẽ mất lợi thế của giao diện?
The_Butcher

4

Bạn thường sử dụng loại có thể sử dụng chung nhất, trong trường hợp này là IList hoặc thậm chí tốt hơn giao diện IEnumerable, để bạn có thể chuyển đổi triển khai thuận tiện sau.

Tuy nhiên, trong .NET 2.0, có một điều khó chịu - IList không có phương thức Sắp xếp () . Bạn có thể sử dụng một bộ chuyển đổi được cung cấp thay thế:

ArrayList.Adapter(list).Sort()

2

Bạn chỉ nên sử dụng giao diện nếu bạn cần, ví dụ: nếu danh sách của bạn được chuyển sang triển khai IList ngoài Danh sách. Điều này đúng khi, ví dụ, bạn sử dụng NHibernate, điều này đưa ILists vào một đối tượng túi NHibernate khi lấy dữ liệu.

Nếu Danh sách là triển khai duy nhất mà bạn sẽ sử dụng cho một bộ sưu tập nhất định, vui lòng khai báo đó là triển khai Danh sách cụ thể.


1

Trong các tình huống tôi thường gặp, tôi hiếm khi sử dụng IList trực tiếp.

Thông thường tôi chỉ sử dụng nó như là một đối số cho một phương thức

void ProcessArrayData(IList almostAnyTypeOfArray)
{
    // Do some stuff with the IList array
}

Điều này sẽ cho phép tôi thực hiện xử lý chung trên hầu hết mọi mảng trong .NET framework, trừ khi nó sử dụng IEnumerable và không phải IList, đôi khi xảy ra.

Nó thực sự đi xuống các loại chức năng bạn cần. Tôi khuyên bạn nên sử dụng lớp List trong hầu hết các trường hợp. IList là tốt nhất khi bạn cần tạo một mảng tùy chỉnh có thể có một số quy tắc rất cụ thể mà bạn muốn gói gọn trong một bộ sưu tập để bạn không lặp lại, nhưng vẫn muốn .NET nhận ra nó như một danh sách.


1

Đối tượng AList cho phép bạn tạo một danh sách, thêm mọi thứ vào nó, xóa nó, cập nhật nó, lập chỉ mục vào nó và vv Danh sách được sử dụng bất cứ khi nào bạn chỉ muốn một Danh sách chung nơi bạn chỉ định loại đối tượng trong đó và đó là nó.

IList mặt khác là một Giao diện. Về cơ bản, nếu bạn muốn tạo loại Danh sách của riêng mình, giả sử một lớp danh sách có tên là BookList, thì bạn có thể sử dụng Giao diện để cung cấp cho bạn các phương thức và cấu trúc cơ bản cho lớp mới của bạn. IList dành cho khi bạn muốn tạo lớp con đặc biệt của riêng mình thực hiện Danh sách.

Một điểm khác biệt là: IList là một Giao diện và không thể khởi tạo. Danh sách là một lớp và có thể được khởi tạo. Nó có nghĩa là:

IList<string> MyList = new IList<string>();

List<string> MyList = new List<string>
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.