Tôi có nên luôn trả về IEnumerable <T> thay vì IList <T> không?


97

Khi tôi đang viết mã DAL hoặc mã khác trả về một tập hợp các mục, tôi có nên thực hiện câu lệnh trả về luôn không:

public IEnumerable<FooBar> GetRecentItems()

hoặc là

public IList<FooBar> GetRecentItems()

Hiện tại, trong mã của tôi, tôi đã cố gắng sử dụng IEnumerable nhiều nhất có thể nhưng tôi không chắc liệu đây có phải là phương pháp hay nhất không? Nó có vẻ đúng bởi vì tôi đã trả lại kiểu dữ liệu chung nhất trong khi vẫn mô tả những gì nó làm, nhưng có lẽ điều này không chính xác.


1
Đó có phải là Danh sách <T> hay IList <T> mà bạn đang hỏi không? Tiêu đề và câu hỏi nói những thứ khác nhau ...
Fredrik Mork

Khi nào có thể giao diện người dùng IEnumerable hoặc Ilist instaead của loại riêng biệt.
Usman Masood

2
Tôi trả về các bộ sưu tập dưới dạng Danh sách <T>. Tôi không thấy cần phải quay trở lại IEnumberable <T> vì bạn có thể trích xuất từ List <T>
Chuck Conway


có thể trùng lặp của ienumerablet-như-trở-type
nawfal

Câu trả lời:


44

Nó thực sự phụ thuộc vào lý do tại sao bạn đang sử dụng giao diện cụ thể đó.

Ví dụ: IList<T>có một số phương thức không có trong IEnumerable<T>:

  • IndexOf(T item)
  • Insert(int index, T item)
  • RemoveAt(int index)

và Thuộc tính:

  • T this[int index] { get; set; }

Nếu bạn cần các phương thức này theo bất kỳ cách nào, thì hãy trả về bằng mọi cách IList<T>.

Ngoài ra, nếu phương thức sử dụng IEnumerable<T>kết quả của bạn đang mong đợi IList<T>, nó sẽ tiết kiệm CLR khỏi việc xem xét bất kỳ chuyển đổi nào được yêu cầu, do đó tối ưu hóa mã đã biên dịch.


2
@Jon FDG khuyên bạn nên sử dụng Bộ sưu tập <T> hoặc ReadOnlyCollection <T> làm giá trị trả về cho các loại bộ sưu tập, hãy xem câu trả lời của tôi.
Sam Saffron

Bạn không rõ ý của mình trong câu cuối cùng khi bạn nói "nó sẽ lưu CLR". Điều gì sẽ lưu nó, sử dụng IEnumerable so với IList? Bạn có thể làm cho điều đó rõ ràng hơn?
Tích

1
@CoffeeAddict Ba năm sau câu trả lời này, tôi nghĩ bạn nói đúng - phần cuối cùng thật mơ hồ. Nếu một phương thức yêu cầu một IList <T> làm tham số nhận được IEnumerable <T> thì IEnumerable phải được bao bọc theo cách thủ công trong một danh sách mới <T> hoặc trình triển khai IList <T> khác và công việc đó sẽ không được thực hiện bởi CLR cho bạn. Ngược lại - một phương thức mong đợi IEnumerable <T> nhận được IList <T>, có thể phải thực hiện một số thao tác mở hộp nhưng nếu nhìn nhận lại thì có thể không cần vì IList <T> triển khai IEnumerable <T>.
Jon Limjap

68

Hướng dẫn thiết kế khung khuyến nghị sử dụng Bộ sưu tập lớp khi bạn cần trả lại một bộ sưu tập có thể sửa đổi bởi người gọi hoặc ReadOnlyCollection cho các bộ sưu tập chỉ đọc.

Lý do này được ưu tiên hơn là đơn giản IListIListnó không thông báo cho người gọi nếu nó chỉ đọc hay không.

Nếu bạn trả về một IEnumerable<T>thay thế, một số thao tác nhất định có thể phức tạp hơn một chút để người gọi thực hiện. Ngoài ra, bạn sẽ không còn cung cấp cho người gọi sự linh hoạt để sửa đổi bộ sưu tập, một cái gì đó mà bạn có thể muốn hoặc có thể không muốn.

Hãy nhớ rằng LINQ có một vài thủ thuật và sẽ tối ưu hóa một số lệnh gọi nhất định dựa trên loại chúng được thực hiện. Vì vậy, ví dụ, nếu bạn thực hiện một Countvà tập hợp bên dưới là một Danh sách, nó sẽ KHÔNG đi qua tất cả các phần tử.

Cá nhân tôi, đối với một ORM tôi có thể sẽ gắn bó với Collection<T>giá trị trả lại của tôi.


12
Các Hướng dẫn cho bộ sưu tập chứa một danh sách chi tiết hơn về Dos và DONTs.
Chaquotay

26

Nói chung, bạn nên yêu cầu những gì chung chung nhất và trả lại những gì cụ thể nhất mà bạn có thể. Vì vậy, nếu bạn có một phương thức nhận tham số và bạn chỉ thực sự cần những gì có sẵn trong IEnumerable, thì đó phải là kiểu tham số của bạn. Nếu phương thức của bạn có thể trả về IList hoặc IEnumerable, bạn nên trả về IList. Điều này đảm bảo rằng nó có thể được sử dụng bởi nhiều người tiêu dùng nhất.

Hãy buông lỏng những gì bạn yêu cầu và rõ ràng trong những gì bạn cung cấp.


1
Tôi đã đi đến kết luận ngược lại: rằng người ta nên chấp nhận các kiểu cụ thể và trả về các kiểu chung. Tuy nhiên, bạn có thể đúng rằng các phương pháp áp dụng rộng rãi hơn tốt hơn các phương pháp hạn chế hơn. Tôi sẽ phải suy nghĩ về điều này nhiều hơn.
Dave Cousineau

3
Lý do cho việc chấp nhận các kiểu chung làm đầu vào là nó cho phép bạn làm việc với phạm vi đầu vào rộng nhất có thể để sử dụng lại nhiều nhất một thành phần có thể. Mặt khác, vì bạn đã biết chính xác loại đồ vật mà bạn có, nên việc che đi nó không có ích lợi gì.
Mel

1
Tôi nghĩ rằng tôi đồng ý với việc có nhiều tham số tổng quát hơn, nhưng lý do của bạn để trả về một cái gì đó ít tổng quát hơn là gì?
Dave Cousineau

6
Được rồi, hãy để tôi thử cách này theo cách khác. Tại sao bạn lại vứt bỏ thông tin? Nếu bạn chỉ quan tâm đến kết quả là IEnumerable <T>, liệu bạn có bị tổn hại gì khi biết rằng đó là IList <T> không? Không, nó không. Nó có thể là thông tin thừa trong một số trường hợp, nhưng nó không gây hại cho bạn. Bây giờ vì lợi ích. Nếu bạn trả lại Danh sách hoặc IList, tôi có thể nói ngay rằng bộ sưu tập đã được truy xuất, điều mà tôi không thể biết được với IEnumerable. Đây có thể là thông tin hữu ích hoặc không nhưng một lần nữa, tại sao bạn lại vứt bỏ thông tin? Nếu bạn biết thêm thông tin về điều gì đó, hãy chuyển nó.
Mel

Nhìn lại, tôi ngạc nhiên là không có ai gọi cho tôi vào cuộc gọi đó. Một IEnumerable SẼ được truy xuất rồi. Tuy nhiên, một IQueryable có thể được trả về ở trạng thái chưa được đánh giá. Vì vậy, có lẽ một ví dụ tốt hơn sẽ trả về IQueryable so với IEnumerable. Trả về IQueryable cụ thể hơn sẽ cho phép tạo thêm thành phần, trong khi trả về IEnumerable sẽ buộc phải liệt kê ngay lập tức và làm giảm khả năng tổng hợp của phương pháp.
Mel

23

Mà phụ thuộc...

Việc trả lại loại có nguồn gốc ít nhất ( IEnumerable) sẽ khiến bạn mất nhiều thời gian nhất để thay đổi cách triển khai cơ bản.

Việc trả về một kiểu dẫn xuất khác ( IList) cung cấp cho người dùng API của bạn nhiều thao tác hơn trên kết quả.

Tôi luôn khuyên bạn nên trả về loại có nguồn gốc ít nhất có tất cả các thao tác mà người dùng của bạn sẽ cần ... vì vậy, về cơ bản, trước tiên bạn phải xác định lại những thao tác nào trên kết quả có ý nghĩa trong ngữ cảnh của API mà bạn đang xác định.


câu trả lời chung chung tốt đẹp vì nó cũng áp dụng cho các phương pháp khác.
user420667

1
Tôi biết câu trả lời này rất cũ ... nhưng nó có vẻ mâu thuẫn với tài liệu: "Nếu cả giao diện IDictionary <TKey, TValue> và giao diện IList <T> đều không đáp ứng yêu cầu của bộ sưu tập bắt buộc, hãy lấy lớp bộ sưu tập mới từ thay vào đó, giao diện ICollection <T> để linh hoạt hơn "Điều này có vẻ ngụ ý rằng kiểu dẫn xuất hơn nên được ưu tiên hơn. ( msdn.microsoft.com/en-us/library/92t2ye13(v=vs.110).aspx ) \
DeborahK

11

Một điều cần xem xét là nếu bạn đang sử dụng câu lệnh LINQ thực thi chậm để tạo IEnumerable<T>, việc gọi .ToList()trước khi bạn quay lại từ phương thức của mình có nghĩa là các mục của bạn có thể được lặp lại hai lần - một lần để tạo Danh sách và một lần khi người gọi lặp lại , bộ lọc hoặc chuyển đổi giá trị trả lại của bạn. Khi thực tế, tôi muốn tránh chuyển đổi kết quả của LINQ-to-Objects thành một Danh sách hoặc Từ điển cụ thể cho đến khi tôi phải làm vậy. Nếu người gọi của tôi cần Danh sách, đó là một phương pháp đơn giản dễ gọi - tôi không cần phải đưa ra quyết định đó cho họ và điều đó làm cho mã của tôi hiệu quả hơn một chút trong trường hợp người gọi chỉ thực hiện một bước trước.


@Joel Mueller, tôi thường gọi ToList () trên họ. Tôi thường không thích để IQueryable vào phần còn lại của các dự án của mình.
KingNestor

4
Tôi đã đề cập nhiều hơn đến LINQ-to-Objects, nơi IQueryable thường không nhập vào bức tranh. Khi một cơ sở dữ liệu có liên quan, ToList () trở nên cần thiết hơn, vì nếu không, bạn có nguy cơ đóng kết nối của mình trước khi lặp lại, điều này không hoạt động rất tốt. Tuy nhiên, khi đó không phải là vấn đề, khá dễ dàng để hiển thị IQueryable dưới dạng IEnumerable mà không cần phải lặp lại khi bạn muốn ẩn IQueryable.
Joel Mueller

9

List<T>cung cấp cho mã gọi nhiều tính năng khác, chẳng hạn như sửa đổi đối tượng trả về và truy cập theo chỉ mục. Vì vậy, câu hỏi đặt ra là: trong trường hợp sử dụng cụ thể của ứng dụng của bạn, bạn CÓ MUỐN hỗ trợ các cách sử dụng như vậy không (có lẽ là bằng cách trả lại một bộ sưu tập mới được xây dựng!), Để thuận tiện cho người gọi - hay bạn muốn tốc độ cho trường hợp đơn giản khi tất cả nhu cầu của người gọi là lặp qua bộ sưu tập và bạn có thể trả về một cách an toàn một tham chiếu đến một bộ sưu tập cơ bản thực sự mà không sợ điều này sẽ khiến nó bị thay đổi sai, v.v.?

Chỉ bạn mới có thể trả lời câu hỏi này và chỉ bằng cách hiểu rõ những gì người gọi của bạn sẽ muốn làm với giá trị trả về và mức độ quan trọng của hiệu suất ở đây (mức độ lớn của các bộ sưu tập bạn sẽ sao chép, khả năng đây là một nút cổ chai, Vân vân).


"trả về một cách an toàn một tham chiếu đến một bộ sưu tập cơ bản thực mà không sợ điều này sẽ khiến nó bị thay đổi sai" - Ngay cả khi bạn trả về IEnumerable <T>, họ không thể chuyển nó trở lại Danh sách <T> và thay đổi nó sao?
Kobi

Không phải mọi IEnumitable <T> cũng là một List <T>. Nếu đối tượng được trả về không thuộc kiểu kế thừa từ List <T> hoặc thực hiện IList <T> thì điều này sẽ dẫn đến một lỗi không hợp lệ.
lowglider

2
List <T> có vấn đề mà nó khóa bạn vào một việc thực hiện đặc biệt Collection <T> hoặc ReadOnlyCollection <T> được ưa thích
Sam Saffron

4

Tôi nghĩ bạn có thể sử dụng một trong hai, nhưng mỗi cái có một cách sử dụng. Về cơ bản ListIEnumerablenhưng bạn có chức năng đếm, thêm phần tử, xóa phần tử

IEnumerable không hiệu quả để đếm các phần tử

Nếu bộ sưu tập được dự định là chỉ đọc, hoặc việc sửa đổi bộ sưu tập được kiểm soát bởi việc Parenttrả lại IListchỉ cho Countkhông phải là một ý kiến ​​hay.

Trong Linq, có một Count()phương thức mở rộng IEnumerable<T>mà bên trong CLR sẽ tắt đến .Countnếu loại cơ bản là của IList, do đó sự khác biệt về hiệu suất là không đáng kể.

Nói chung, tôi cảm thấy (ý kiến) tốt hơn nên trả lại IEnumerable nếu có thể, nếu bạn cần bổ sung thì hãy thêm các phương thức này vào lớp cha, nếu không, người tiêu dùng sau đó đang quản lý bộ sưu tập trong Model vi phạm các nguyên tắc, ví dụ: manufacturer.Models.Add(model)vi phạm luật đồng hồ đo. Tất nhiên đây chỉ là những hướng dẫn chứ không phải là những quy tắc khó và nhanh, nhưng cho đến khi bạn nắm được đầy đủ khả năng áp dụng, hãy làm theo một cách mù quáng còn hơn là không tuân theo chút nào.

public interface IManufacturer 
{
     IEnumerable<Model> Models {get;}
     void AddModel(Model model);
}

(Lưu ý: Nếu sử dụng nNHibernate, bạn có thể cần ánh xạ tới IList riêng bằng các trình truy cập khác nhau.)


2

Nó không đơn giản như vậy khi bạn đang nói về giá trị trả về thay vì tham số đầu vào. Khi đó là một tham số đầu vào, bạn biết chính xác những gì bạn cần làm. Vì vậy, nếu bạn cần có thể lặp lại bộ sưu tập, bạn lấy IEnumberable trong khi nếu bạn cần thêm hoặc bớt, bạn lấy IList.

Trong trường hợp giá trị trả về, nó khó hơn. Người gọi của bạn mong đợi điều gì? Nếu bạn trả lại một IEnumerable, thì anh ta sẽ không biết trước rằng anh ta có thể đưa ra một IList từ nó. Tuy nhiên, nếu bạn trả lại một IList, anh ta sẽ biết rằng anh ta có thể lặp lại nó. Vì vậy, bạn phải tính đến những gì người gọi của bạn sẽ làm với dữ liệu. Chức năng mà người gọi của bạn cần / mong đợi là những gì sẽ chi phối khi đưa ra quyết định trả lại cái gì.


0

như tất cả đã nói, điều đó còn tùy thuộc, nếu bạn không muốn Thêm / Xóa chức năng ở lớp gọi thì tôi sẽ bỏ phiếu cho IEnumerable vì nó chỉ cung cấp tính năng lặp lại và chức năng cơ bản mà tôi thích. Gửi lại IList phiếu bầu của tôi luôn luôn ủng hộ nó nhưng chủ yếu là những gì bạn thích và những gì không. về hiệu suất, tôi nghĩ chúng giống nhau hơn.


0

Nếu bạn không tính vào mã bên ngoài của mình, tốt hơn hết là trả về IEnumerable, vì sau này bạn có thể thay đổi cách triển khai của mình (mà không cần tác động mã bên ngoài), ví dụ, đối với logic trình lặp lợi nhuận và tiết kiệm tài nguyên bộ nhớ (nhân tiện, tính năng ngôn ngữ rất tốt ).

Tuy nhiên, nếu bạn cần số lượng mục, đừng quên rằng có một lớp khác giữa IEnumerable và IList - ICollection .


0

Tôi có thể hơi hụt hẫng ở đây, thấy rằng không có ai khác đề xuất nó cho đến nay, nhưng tại sao bạn không trả lại (I)Collection<T>?

Theo những gì tôi nhớ, Collection<T>kiểu trả về được ưa thích hơn List<T>vì nó trừu tượng hóa việc triển khai. Tất cả họ đều thực hiện IEnumerable, nhưng với tôi điều đó nghe có vẻ hơi quá thấp đối với công việc.


0

Tôi nghĩ bạn có thể sử dụng một trong hai, nhưng mỗi cái có một cách sử dụng. Về cơ bản ListIEnumerablenhưng bạn có chức năng đếm, Thêm phần tử, xóa phần tử

IEnumerable không hiệu quả để đếm các phần tử hoặc lấy một phần tử cụ thể trong tập hợp.

List là một tập hợp lý tưởng để tìm các phần tử cụ thể, dễ dàng thêm hoặc loại bỏ các phần tử đó.

Nói chung, tôi cố gắng sử dụng Listnếu có thể vì điều này giúp tôi linh hoạt hơn.

Sử dụng List<FooBar> getRecentItems() thay vì IList<FooBar> GetRecentItems()


0

Tôi nghĩ quy tắc chung là sử dụng lớp cụ thể hơn để trả về, để tránh thực hiện công việc không cần thiết và cung cấp cho người gọi của bạn nhiều tùy chọn hơn.

Điều đó nói rằng, tôi nghĩ điều quan trọng hơn là xem xét mã trước mặt bạn mà bạn đang viết hơn là mã mà người tiếp theo sẽ viết (trong lý do.) Điều này là bởi vì bạn có thể đưa ra giả định về mã đã tồn tại.

Hãy nhớ rằng việc chuyển LÊN một bộ sưu tập từ IEnumerable trong một giao diện sẽ hoạt động, việc chuyển xuống IEnumerable từ một bộ sưu tập sẽ phá vỡ mã hiện có.

Nếu tất cả các ý kiến ​​này có vẻ mâu thuẫn, đó là vì quyết định này là chủ quan.


0

TL; DR; - tóm lược

  • Nếu bạn phát triển phần mềm nội bộ, hãy sử dụng loại cụ thể (Like List) cho các giá trị trả về và loại chung nhất cho các tham số đầu vào ngay cả trong trường hợp tập hợp.
  • Nếu một phương thức là một phần của API công cộng của thư viện có thể phân phối lại, hãy sử dụng các giao diện thay vì các kiểu tập hợp cụ thể để giới thiệu cả giá trị trả về và tham số đầu vào.
  • Nếu một phương thức trả về một tập hợp chỉ đọc, hãy hiển thị điều đó bằng cách sử dụng IReadOnlyListhoặc IReadOnlyCollectionlàm kiểu giá trị trả về.

Hơn

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.