Trả lại 'IList' vs 'ICollection' vs 'Bộ sưu tập'


119

Tôi bối rối về loại bộ sưu tập mà tôi nên trả về từ các phương thức và thuộc tính API công khai của mình.

Các bộ sưu tập mà tôi có trong tâm trí là IList, ICollectionCollection.

Việc trả lại một trong những loại này luôn được ưu tiên hơn những loại khác hay nó phụ thuộc vào tình hình cụ thể?



Câu trả lời:


71

Nói chung, bạn nên trả về một kiểu càng chung chung càng tốt, tức là một kiểu biết vừa đủ dữ liệu trả về mà người tiêu dùng cần sử dụng. Bằng cách đó, bạn có nhiều quyền tự do hơn để thay đổi việc triển khai API mà không vi phạm mã đang sử dụng nó.

Cũng xem xét IEnumerable<T>giao diện như kiểu trả lại. Nếu kết quả chỉ được lặp lại, người tiêu dùng không cần nhiều hơn thế.


25
Nhưng cũng nên xem xét ICollection <T> mở rộng IEnumerable <T> để cung cấp các tiện ích bổ sung bao gồm thuộc tính Count. Điều này có thể hữu ích vì bạn có thể xác định độ dài của trình tự mà không cần duyệt trình tự nhiều lần.
retrodrone

11
@retrodrone: Trên thực tế, việc triển khai Countphương thức kiểm tra kiểu thực sự của tập hợp, vì vậy nó sẽ sử dụng Lengthhoặc Countthuộc tính cho một số kiểu đã biết như mảng và ICollectionngay cả khi bạn cung cấp nó dưới dạng một IEnumerable.
Guffa

8
@Guffa: Có thể đáng lưu ý rằng nếu một lớp Thing<T>triển khai IList<T>nhưng không phải là lớp không chung chung ICollection, thì việc gọi IEnumerable<Cat>.Count()a Thing<Cat>sẽ nhanh, nhưng việc gọi IEnumerable<Animal>.Count()sẽ chậm (vì phương thức mở rộng sẽ tìm kiếm và không tìm thấy, một triển khai của ICollection<Cat>). ICollectionTuy nhiên, nếu lớp triển khai không chung chung , IEnumerable<Animal>.Countsẽ tìm và sử dụng nó.
supercat


8
Tôi không đồng ý. Tốt nhất bạn nên trả lại loại giàu nhất mà bạn có để khách hàng có thể sử dụng trực tiếp nếu đó là thứ họ muốn. Nếu bạn có một Danh sách <>, tại sao lại trả về một IEnuerable <> chỉ để buộc người gọi gọi ToList () một cách không cần thiết?
zumalifeguard

140

ICollection<T>là một giao diện đó cho thấy nhiều ngữ nghĩa bộ sưu tập như Add(), Remove(), và Count.

Collection<T>là một triển khai cụ thể của ICollection<T>giao diện.

IList<T>về cơ bản là một ICollection<T>với quyền truy cập dựa trên thứ tự ngẫu nhiên.

Trong trường hợp này, bạn nên quyết định xem kết quả của mình có yêu cầu ngữ nghĩa danh sách như lập chỉ mục dựa trên thứ tự (sau đó sử dụng IList<T>) hay không hoặc bạn chỉ cần trả về một "túi" kết quả không có thứ tự (sau đó sử dụng ICollection<T>).


5
Collection<T>nông cụ IList<T>và không chỉ ICollection<T>.
CodesInChaos

56
Tất cả các bộ sưu tập trong .Net được sắp xếp, bởi vì IEnumerable<T>được sắp xếp. Điều gì phân biệt IList<T>từ ICollection<T>là nó cung cấp các thành viên để làm việc với các chỉ số. Ví dụ list[5]hoạt động, nhưng collection[5]sẽ không biên dịch.
svick

5
Tôi cũng không đồng ý rằng các bộ sưu tập có thứ tự nên thực hiện IList<T>. Có rất nhiều bộ sưu tập được đặt hàng nhưng không. IList<T>là về truy cập được lập chỉ mục nhanh chóng.
CodesInChaos

4
@yoyo Các lớp và giao diện của bộ sưu tập .net được thiết kế đơn giản và đặt tên tồi. Tôi sẽ không nghĩ quá nhiều về việc tại sao họ lại đặt tên chúng như vậy.
CodesInChaos

6
@svick: Có phải tất cả các bộ sưu tập đều được đặt hàng vì IEnumerable<T>được đặt hàng không? Take HashSet<T>- nó thực hiện IEnumerable<T>nhưng rõ ràng là không được đặt hàng. Tức là bạn không có ảnh hưởng đến thứ tự của các mục và thứ tự này có thể thay đổi bất cứ lúc nào, nếu bảng băm nội bộ được tổ chức lại.
Olivier Jacot-Descombes

61

Sự khác biệt chính giữa IList<T>ICollection<T>IList<T>cho phép bạn truy cập các phần tử thông qua một chỉ mục. IList<T>mô tả các kiểu giống mảng. Các phần tử trong một ICollection<T>chỉ có thể được truy cập thông qua phép liệt kê. Cả hai đều cho phép chèn và xóa các phần tử.

Nếu bạn chỉ cần liệt kê một bộ sưu tập, thì IEnumerable<T>bạn nên chọn. Nó có hai lợi thế so với những cái khác:

  1. Nó không cho phép các thay đổi đối với bộ sưu tập (nhưng không cho phép các phần tử, nếu chúng thuộc loại tham chiếu).

  2. Nó cho phép nhiều nguồn nhất có thể, bao gồm cả các bảng liệt kê được tạo theo thuật toán và hoàn toàn không phải là bộ sưu tập.

Collection<T>là một lớp cơ sở chủ yếu hữu ích cho những người triển khai các tập hợp. Nếu bạn hiển thị nó trong các giao diện (API), nhiều bộ sưu tập hữu ích không bắt nguồn từ nó sẽ bị loại trừ.


Một nhược điểm IList<T>là mảng thực hiện nó nhưng không cho phép bạn thêm hoặc bớt các mục (tức là bạn không thể thay đổi độ dài mảng). Một ngoại lệ sẽ được ném ra nếu bạn gọi IList<T>.Add(item)một mảng. Tình hình phần nào được xoa dịu vì IList<T>có thuộc tính Boolean IsReadOnlymà bạn có thể kiểm tra trước khi thử làm như vậy. Nhưng trong mắt tôi, đây vẫn là một lỗ hổng thiết kế trong thư viện . Do đó, tôi sử dụng List<T>trực tiếp, khi khả năng thêm hoặc bớt các mục được yêu cầu.


Mã Phân tích (và FxCop) khuyên rằng khi trở về một bộ sưu tập chung bê tông, một trong những điều sau đây nên được trả lại: Collection, ReadOnlyCollectionhoặc KeyedCollection. Và điều đó Listkhông nên được trả lại.
DavidRR

@DavidRR: Thường sẽ hữu ích khi chuyển một danh sách tới một phương thức phải thêm hoặc bớt các mục. Nếu bạn nhập tham số là IList<T>, không có cách nào để đảm bảo rằng phương thức sẽ không được gọi với một mảng là tham số.
Olivier Jacot-Descombes

7

IList<T>là giao diện cơ sở cho tất cả các danh sách chung. Vì nó là một bộ sưu tập có thứ tự, nên việc triển khai có thể quyết định thứ tự, từ thứ tự đã sắp xếp đến thứ tự chèn. Hơn nữa, Ilistcó thuộc tính Item cho phép các phương thức đọc và chỉnh sửa các mục trong danh sách dựa trên chỉ mục của chúng. Điều này giúp bạn có thể chèn, xóa một giá trị vào / khỏi danh sách tại một chỉ mục vị trí.

Ngoài ra IList<T> : ICollection<T>, tất cả các phương pháp từ ICollection<T>cũng có sẵn ở đây để thực hiện.

ICollection<T>là giao diện cơ sở cho tất cả các tập hợp chung. Nó xác định kích thước, điều tra viên và phương pháp đồng bộ hóa. Bạn có thể thêm hoặc xóa một mục vào một bộ sưu tập nhưng bạn không thể chọn vị trí nó xảy ra do không có thuộc tính chỉ mục.

Collection<T>cung cấp một thực hiện cho IList<T>, IListIReadOnlyList<T>.

Nếu bạn sử dụng loại giao diện hẹp hơn, chẳng hạn như ICollection<T>thay vì IList<T>, bạn sẽ bảo vệ mã của mình khỏi những thay đổi vi phạm. Nếu bạn sử dụng một loại giao diện rộng hơn chẳng hạn IList<T>, bạn có nhiều nguy cơ bị phá vỡ các thay đổi mã hơn.

Trích dẫn từ một nguồn ,

ICollection, ICollection<T>: Bạn muốn sửa đổi bộ sưu tập hoặc bạn quan tâm đến kích thước của nó. IList, IList<T>: Bạn muốn sửa đổi bộ sưu tập và bạn quan tâm đến thứ tự và / hoặc vị trí của các phần tử trong bộ sưu tập.


5

Trả lại một loại giao diện chung chung hơn, vì vậy (thiếu thông tin thêm về trường hợp sử dụng cụ thể của bạn) tôi muốn hướng tới điều đó. Nếu bạn muốn hiển thị hỗ trợ lập chỉ mục, hãy chọn IList<T>, nếu không ICollection<T>sẽ đủ. Cuối cùng, nếu bạn muốn chỉ ra rằng các kiểu trả về chỉ được đọc, hãy chọn IEnumerable<T>.

Và, trong trường hợp bạn chưa đọc nó trước đây, Brad Abrams và Krzysztof Cwalina đã viết một cuốn sách tuyệt vời có tiêu đề "Hướng dẫn thiết kế khung: Quy ước, thành ngữ và mẫu cho thư viện .NET có thể tái sử dụng" (bạn có thể tải xuống bản tóm tắt từ đây ).


IEnumerable<T>là chỉ đọc? Chắc chắn rồi nhưng khi khôi phục nó trong ngữ cảnh của kho lưu trữ, ngay cả sau khi thực thi ToList cũng không an toàn cho luồng. Vấn đề là khi nguồn dữ liệu gốc nhận được GC thì IEnumarble.ToList () không hoạt động trong ngữ cảnh đa luồng. Nó hoạt động trên một tuyến tính một becasue GC được gọi sau khi bạn thực hiện công việc nhưng việc sử dụng asyncbây giờ một ngày trong phiên bản 4.5+ IEnumarbale đang trở nên nhức nhối.
Piotr Kula,

-3

Có một số chủ đề xuất phát từ câu hỏi này:

  • giao diện so với các lớp
  • lớp cụ thể nào, từ một số lớp giống nhau, tập hợp, danh sách, mảng?
  • Các lớp chung so với tập hợp con ("generics")

Bạn có thể muốn nhấn mạnh rằng API hướng đối tượng của nó

giao diện so với các lớp

Nếu bạn không có nhiều kinh nghiệm với các giao diện, tôi khuyên bạn nên theo học. Tôi thấy rất nhiều lần các nhà phát triển chuyển sang giao diện, ngay cả khi nó không phải là hoàn toàn khác.

Và, hãy chấm dứt việc thiết kế giao diện kém, thay vào đó là một thiết kế lớp tốt, nhân tiện, cuối cùng có thể được chuyển sang một thiết kế giao diện tốt ...

Bạn sẽ thấy rất nhiều giao diện trong API, nhưng đừng vội sử dụng nó, nếu bạn không cần.

Cuối cùng, bạn sẽ học cách áp dụng các giao diện vào mã của mình.

lớp cụ thể nào, từ một số lớp giống nhau, tập hợp, danh sách, mảng?

Có một số lớp trong c # (dotnet) có thể được hoán đổi cho nhau. Như đã đề cập, nếu bạn cần thứ gì đó từ một lớp cụ thể hơn, chẳng hạn như "CanBeSortedClass", thì hãy làm cho nó rõ ràng trong API của bạn.

Người dùng API của bạn có thực sự cần biết rằng lớp của bạn có thể được sắp xếp hay áp dụng một số định dạng cho các phần tử không? Sau đó sử dụng "CanBeSortedClass" hoặc "ElementsCanBePaintedClass", nếu không thì sử dụng "GenericBrandClass".

Nếu không, hãy sử dụng một lớp tổng quát hơn.

Các lớp tập hợp chung so với tập hợp con ("generics")

Bạn sẽ thấy rằng có những lớp chứa các phần tử khác và bạn có thể chỉ định rằng tất cả các phần tử phải thuộc một loại cụ thể.

Bộ sưu tập Chung là những lớp mà bạn có thể sử dụng cùng một bộ sưu tập, cho một số ứng dụng mã, mà không cần phải tạo một bộ sưu tập mới, cho mỗi loại subitem mới, như sau: Bộ sưu tập .

Người dùng API của bạn có cần một loại rất cụ thể, giống nhau cho tất cả các phần tử không?

Sử dụng một cái gì đó như List<WashingtonApple>.

Người dùng API của bạn có cần một số loại liên quan không?

Vạch trần List<Fruit>cho API của bạn, và sử dụng List<Orange> List<Banana>, List<Strawberry>trong nội bộ, nơi Orange, BananaStrawberrylà hậu duệ từ Fruit.

Người dùng API của bạn có cần một bộ sưu tập kiểu chung không?

Sử dụng List, nơi có object(các) mục.

Chúc mừng.


7
"Nếu bạn không có nhiều kinh nghiệm với các giao diện, tôi khuyên bạn nên theo học các lớp" Đây không phải là lời khuyên tốt. Điều đó giống như nói rằng nếu có điều gì đó bạn không biết, hãy tránh xa nó. Các giao diện cực kỳ mạnh mẽ và hỗ trợ khái niệm "Chương trình để trừu tượng hóa và cụ thể hóa" Chúng cũng khá mạnh mẽ để thực hiện kiểm thử đơn vị vì khả năng trao đổi các loại thông qua mô phỏng. Có rất nhiều lý do tuyệt vời khác để sử dụng Giao diện ngoài những nhận xét này, vì vậy hãy bằng mọi cách khám phá chúng.
atconway

@atconway Tôi sử dụng giao diện thông thường, nhưng, theo nhiều khái niệm, nó là một công cụ mạnh mẽ, nó có thể dễ dàng kết hợp sử dụng. Và, đôi khi, nhà phát triển có thể không cần nó. Đề xuất của tôi không phải là "đừng bao giờ giao diện", đề xuất của tôi là "trong trường hợp này, bạn có thể bỏ qua các giao diện. Tìm hiểu về nó và bạn có thể tận dụng chúng sau này". Chúc mừng.
umlcat

1
Tôi khuyên bạn nên luôn lập trình cho một giao diện. Nó giúp giữ cho mã được ghép nối lỏng lẻo.
mac10688

@ mac10688 Câu trả lời trước đây của tôi (atconway) cũng áp dụng cho nhận xét của bạn.
umlcat
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.