Bộ sưu tập <T> so với Danh sách <T> bạn nên sử dụng gì trên giao diện của mình?


162

Mã này trông như dưới đây:

namespace Test
{
    public interface IMyClass
    {
        List<IMyClass> GetList();
    }

    public class MyClass : IMyClass
    {
        public List<IMyClass> GetList()
        {
            return new List<IMyClass>();
        }
    }
}

Khi tôi chạy Phân tích mã, tôi nhận được khuyến nghị sau.

Cảnh báo 3 CA1002: Microsoft.Design: Thay đổi 'Danh sách' trong 'IMyClass.GetList ()' để sử dụng Bộ sưu tập, ReadOnlyCollection hoặc KeyedCollection

Làm thế nào tôi nên sửa lỗi này và thực hành tốt ở đây là gì?

Câu trả lời:


234

Để trả lời phần "tại sao" của câu hỏi là tại sao không List<T>, Lý do là bằng chứng trong tương lai và tính đơn giản của API.

Chứng minh trong tương lai

List<T>không được thiết kế để có thể dễ dàng mở rộng bằng cách phân lớp nó; nó được thiết kế để nhanh chóng thực hiện nội bộ. Bạn sẽ nhận thấy các phương thức trên nó không phải là ảo và do đó không thể bị ghi đè và không có móc nối nào trong Add/ Insert/ Removehoạt động của nó .

Điều này có nghĩa là nếu bạn cần thay đổi hành vi của bộ sưu tập trong tương lai (ví dụ: từ chối các đối tượng null mà mọi người cố gắng thêm hoặc thực hiện công việc bổ sung khi điều này xảy ra như cập nhật trạng thái lớp của bạn) thì bạn cần thay đổi loại của bộ sưu tập bạn quay lại một lớp bạn có thể phân lớp, đây sẽ là một thay đổi giao diện vi phạm (tất nhiên thay đổi ngữ nghĩa của những thứ như không cho phép null cũng có thể là thay đổi giao diện, nhưng những thứ như cập nhật trạng thái lớp bên trong của bạn sẽ không).

Vì vậy, bằng cách trả về một lớp có thể dễ dàng được phân lớp như Collection<T>hoặc một giao diện như IList<T>, ICollection<T>hoặc IEnumerable<T>bạn có thể thay đổi triển khai nội bộ của mình thành một loại bộ sưu tập khác để đáp ứng nhu cầu của bạn, mà không phá vỡ mã của người tiêu dùng vì vẫn có thể được trả lại như loại họ đang mong đợi.

Đơn giản API

List<T>chứa rất nhiều hoạt động hữu ích như BinarySearch, Sortv.v. Tuy nhiên nếu đây là bộ sưu tập bạn đang trưng bày thì có khả năng bạn sẽ kiểm soát ngữ nghĩa của danh sách chứ không phải người tiêu dùng. Vì vậy, trong khi lớp học của bạn có thể cần các hoạt động này, rất khó có khả năng người tiêu dùng của lớp bạn muốn (hoặc thậm chí nên) gọi chúng.

Như vậy, bằng cách cung cấp lớp bộ sưu tập hoặc giao diện đơn giản hơn, bạn giảm số lượng thành viên mà người dùng API của bạn nhìn thấy và giúp họ sử dụng dễ dàng hơn.


1
Phản ứng này là chết trên. Một tốt đọc về đề tài này có thể được tìm thấy ở đây: blogs.msdn.com/fxcop/archive/2006/04/27/...
senfo

7
Tôi thấy những điểm đầu tiên của bạn, nhưng tôi không biết liệu tôi có đồng ý về phần đơn giản API của bạn không.
Boris Callens

stackoverflow.com/a/398988/2632991 Đây cũng là một bài viết hay, về sự khác biệt giữa Bộ sưu tập và Danh sách.
El Mac

2
blog.msdn.com/fxcop/archive/2006/04/27/ - - Đã chết. Chúng ta có một thông tin mới hơn cho điều này?
Machado


50

Cá nhân tôi sẽ tuyên bố nó sẽ trả về một giao diện chứ không phải là một bộ sưu tập cụ thể. Nếu bạn thực sự muốn truy cập danh sách, sử dụng IList<T>. Nếu không, hãy xem xét ICollection<T>IEnumerable<T>.


1
IList sau đó sẽ mở rộng giao diện ICollection?
bovium

8
@Jon: Tôi biết cái này đã cũ, nhưng bạn có thể nhận xét về những gì Krzysztof nói tại blog.msdn.com/b/kcwalina/archive/2005/09/26/474010.aspx không? Cụ thể bình luận của anh ấy, We recommend using Collection<T>, ReadOnlyCollection<T>, or KeyedCollection<TKey,TItem> for outputs and properties and interfaces IEnumerable<T>, ICollection<T>, IList<T> for inputs. CA1002 dường như đi cùng với ý kiến ​​của Krzysztof. Tôi không thể tưởng tượng được tại sao một bộ sưu tập cụ thể sẽ được đề xuất thay vì giao diện và tại sao lại có sự khác biệt giữa đầu vào / đầu ra.
Nelson Rothermel

3
@Nelson: Thật hiếm khi bạn muốn yêu cầu người gọi vượt qua trong một danh sách không thay đổi, nhưng thật hợp lý để trả lại một cái để họ biết rằng nó chắc chắn là bất biến. Không chắc chắn về các bộ sưu tập khác mặc dù. Sẽ tốt đẹp để có thêm chi tiết.
Jon Skeet

2
Nó không dành cho một trường hợp cụ thể. Rõ ràng nói chung ReadOnlyCollection<T>sẽ không có ý nghĩa cho một đầu vào. Tương tự, IList<T>như một đầu vào nói, "Tôi cần Sắp xếp () hoặc một số thành viên khác mà IList có" không có ý nghĩa đối với đầu ra. Nhưng những gì tôi muốn nói là, tại sao sẽ ICollection<T>được khuyến cáo như là một đầu vào và Collection<T>như một đầu ra. Tại sao không sử dụng ICollection<T>như đầu ra cũng như bạn đề xuất?
Nelson Rothermel

9
Tôi nghĩ rằng nó có liên quan đến sự không rõ ràng. Collection<T>ReadOnlyCollection<T>cả hai đều xuất phát từ ICollection<T>(tức là không có IReadOnlyCollection<T>). Nếu bạn trả lại giao diện, không rõ đó là giao diện nào và liệu nó có thể được sửa đổi hay không. Dù sao, cảm ơn cho đầu vào của bạn. Đây là một kiểm tra vệ sinh tốt cho tôi.
Nelson Rothermel

3

Tôi không nghĩ có ai đã trả lời phần "tại sao" chưa ... vậy là xong. Lý do "tại sao" bạn "nên" sử dụng Collection<T>thay vì a List<T>là vì nếu bạn để lộ a List<T>, thì bất kỳ ai có quyền truy cập vào đối tượng của bạn đều có thể sửa đổi các mục trong danh sách. Trong khi đó Collection<T>có nghĩa vụ phải chỉ ra rằng bạn đang thực hiện các phương thức "Thêm", "Xóa", v.v.

Bạn có thể không cần phải lo lắng về điều đó, vì có lẽ bạn chỉ mã hóa giao diện cho chính mình (hoặc có thể là một vài trường đại học). Đây là một ví dụ khác có thể có ý nghĩa.

Nếu bạn có một mảng công khai, ví dụ:

public int[] MyIntegers { get; }

Bạn sẽ nghĩ rằng bởi vì chỉ có một trình truy cập "có được" mà không ai có thể gây rối với các giá trị, nhưng điều đó không đúng. Bất cứ ai cũng có thể thay đổi các giá trị bên trong như thế này:

someObject.MyIngegers[3] = 12345;

Cá nhân, tôi sẽ chỉ sử dụng List<T>trong hầu hết các trường hợp. Nhưng nếu bạn đang thiết kế một thư viện lớp mà bạn sẽ cung cấp cho các nhà phát triển ngẫu nhiên và bạn cần dựa vào trạng thái của các đối tượng ... thì bạn sẽ muốn tạo Bộ sưu tập của riêng mình và khóa nó từ đó: )


"Nếu bạn trả lại Danh sách <T> cho mã máy khách, bạn sẽ không bao giờ có thể nhận được thông báo khi mã máy khách sửa đổi bộ sưu tập." - FX COP ... Xem thêm " msdn.microsoft.com/en-us/l Library / 0ssss9skc.aspx " ... wow, có vẻ như tôi không rời cơ sở sau tất cả :)
Timothy Khouri

Wow - nhiều năm sau tôi thấy rằng liên kết tôi đã dán trong nhận xét ở trên có một trích dẫn ở cuối của nó, vì vậy nó không hoạt động ... msdn.microsoft.com/en-us/l Library / 0fss9skc.aspx
Timothy Khouri

2

Chủ yếu là về việc trừu tượng hóa các triển khai của riêng bạn thay vì phơi bày đối tượng Danh sách để được thao tác trực tiếp.

Sẽ không tốt nếu để các đối tượng (hoặc người) khác sửa đổi trạng thái của các đối tượng của bạn trực tiếp. Hãy suy nghĩ getters / setters tài sản.

Bộ sưu tập -> Đối với bộ sưu tập bình thường
ReadOnlyCollection -> Dành cho các bộ sưu tập không nên sửa đổi
KeyedCollection -> Khi bạn muốn từ điển thay thế.

Làm thế nào để sửa nó phụ thuộc vào những gì bạn muốn lớp của bạn làm và mục đích của phương thức GetList (). Bạn có thể xây dựng?


1
Nhưng Bộ sưu tập <T> và KeyedCollection <,> cũng không chỉ đọc.
nawfal

@nawfal Và tôi đã nói nó ở đâu?
chakrit

1
Bạn tuyên bố không để các đối tượng (hoặc người) khác sửa đổi trạng thái của các đối tượng của bạn trực tiếp và liệt kê 3 loại bộ sưu tập. Chặn ReadOnlyCollectionhai người kia không tuân theo.
nawfal

Đó là nói đến "thực hành tốt". Đúng bối cảnh xin vui lòng. Danh sách đó dưới đây chỉ đơn giản nêu ra yêu cầu cơ bản của các loại như vậy vì OP muốn hiểu cảnh báo. Sau đó tôi tiếp tục hỏi anh ta về mục đích GetList()để có thể giúp anh ta chính xác hơn.
chakrit

1
Ok tôi hiểu phần đó. Nhưng vẫn là phần lý luận không phải là âm thanh theo tôi. Bạn nói Collection<T>giúp trong việc trừu tượng hóa việc thực hiện nội bộ và ngăn chặn thao tác trực tiếp của danh sách nội bộ. Làm sao? Collection<T>chỉ đơn thuần là một trình bao bọc, hoạt động trên cùng một ví dụ được thông qua. Đó là một bộ sưu tập động dành cho thừa kế, không có gì khác (câu trả lời của Greg có liên quan hơn ở đây).
nawfal

1

Trong những trường hợp như vậy, tôi thường cố gắng tiết lộ số lượng thực hiện ít nhất cần thiết. Nếu người tiêu dùng không cần biết rằng bạn thực sự đang sử dụng danh sách thì bạn không cần phải trả lại danh sách. Bằng cách quay lại như Microsoft gợi ý Bộ sưu tập, bạn che giấu sự thật rằng bạn đang sử dụng danh sách từ những người tiêu dùng của lớp bạn và cách ly họ trước một thay đổi nội bộ.


1

Một cái gì đó để thêm mặc dù đã lâu rồi kể từ khi điều này được hỏi.

Khi loại danh sách của bạn xuất phát từ List<T>thay vì Collection<T>, bạn không thể thực hiện các phương thức ảo được bảo vệ Collection<T>thực hiện. Điều này có nghĩa là loại dẫn xuất của bạn không thể phản hồi trong trường hợp bất kỳ sửa đổi nào được thực hiện cho danh sách. Điều này là do List<T>giả sử bạn biết khi bạn thêm hoặc xóa các mục. Có thể phản hồi thông báo là một chi phí và do đó List<T>không cung cấp nó.

Trong trường hợp khi mã bên ngoài có quyền truy cập vào bộ sưu tập của bạn, bạn có thể không kiểm soát được khi nào một mục được thêm hoặc xóa. Do đó Collection<T>cung cấp một cách để biết khi nào danh sách của bạn đã được sửa đổi.


0

Tôi không thấy có vấn đề gì với việc trả lại một cái gì đó như

this.InternalData.Filter(crteria).ToList();

Nếu tôi trả lại một bản sao dữ liệu nội bộ bị ngắt kết nối hoặc kết quả tách rời của truy vấn dữ liệu - tôi có thể trả về một cách an toàn List<TItem>mà không để lộ bất kỳ chi tiết triển khai nào và cho phép sử dụng dữ liệu được trả về một cách thuận tiện.

Nhưng điều này phụ thuộc vào loại người tiêu dùng tôi mong đợi - nếu đây là một cái gì đó giống như lưới dữ liệu tôi muốn trả lại IEnumerable<TItem> , đây sẽ là danh sách các mục được sao chép trong hầu hết các trường hợp :)


-1

Vâng, lớp Collection thực sự chỉ là một lớp bao bọc xung quanh các bộ sưu tập khác để ẩn chi tiết triển khai và các tính năng khác của chúng. Tôi nghĩ điều này có liên quan đến đặc tính ẩn mẫu mã hóa trong các ngôn ngữ hướng đối tượng.

Tôi nghĩ bạn không nên lo lắng về nó, nhưng nếu bạn thực sự muốn làm hài lòng công cụ phân tích mã, chỉ cần làm như sau:

//using System.Collections.ObjectModel;

Collection<MyClass> myCollection = new Collection<MyClass>(myList);

1
Xin lỗi, đó là một lỗi đánh máy. Bộ sưu tập menat <MyClass>. Tôi thực sự mong muốn có được bàn tay của mình trên C # 4 và hiệp phương sai chung btw!
Tamas Czinege

Gói trong một Collection<T>bảo vệ không phải bản thân nó cũng như bộ sưu tập cơ bản theo như tôi hiểu.
nawfal

Đoạn mã đó là "làm ơn cho công cụ phân tích mã". Tôi không nghĩ @TamasCzinege nói bất cứ nơi nào sử dụng Collection<T>sẽ ngay lập tức bảo vệ bộ sưu tập cơ bản của bạn.
chakrit
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.