ReadOnlyCollection hoặc IEnumerable để trưng bày các bộ sưu tập thành viên?


124

Có bất kỳ lý do nào để hiển thị một bộ sưu tập nội bộ dưới dạng ReadOnlyCollection chứ không phải là IEnumerable nếu mã cuộc gọi chỉ lặp lại trên bộ sưu tập?

class Bar
{
    private ICollection<Foo> foos;

    // Which one is to be preferred?
    public IEnumerable<Foo> Foos { ... }
    public ReadOnlyCollection<Foo> Foos { ... }
}


// Calling code:

foreach (var f in bar.Foos)
    DoSomething(f);

Như tôi thấy, IEnumerable là một tập hợp con của giao diện ReadOnlyCollection và nó không cho phép người dùng sửa đổi bộ sưu tập. Vì vậy, nếu giao diện IEnumberable là đủ thì đó là giao diện để sử dụng. Đó có phải là một cách suy luận đúng đắn về nó hay tôi đang thiếu một cái gì đó?

Cảm ơn / Erik


6
Nếu bạn đang sử dụng .NET 4.5, bạn có thể muốn thử các giao diện bộ sưu tập chỉ đọc mới . Bạn vẫn muốn bọc bộ sưu tập được trả lại ReadOnlyCollectionnếu bạn bị hoang tưởng, nhưng bây giờ bạn không bị ràng buộc với việc triển khai cụ thể.
StriplingWar Warrior

Câu trả lời:


96

Giải pháp hiện đại hơn

Trừ khi bạn cần bộ sưu tập nội bộ có thể thay đổi, bạn có thể sử dụng System.Collections.Immutablegói, thay đổi loại trường của bạn thành một bộ sưu tập bất biến, và sau đó phơi bày điều đó trực tiếp - tất nhiên là giả sử Foonó là bất biến.

Cập nhật câu trả lời để giải quyết câu hỏi trực tiếp hơn

Có bất kỳ lý do nào để hiển thị một bộ sưu tập nội bộ dưới dạng ReadOnlyCollection chứ không phải là IEnumerable nếu mã cuộc gọi chỉ lặp lại trên bộ sưu tập?

Nó phụ thuộc vào mức độ bạn tin tưởng mã gọi. Nếu bạn hoàn toàn kiểm soát mọi thứ sẽ gọi thành viên này và bạn đảm bảo rằng sẽ không có mã nào sử dụng:

ICollection<Foo> evil = (ICollection<Foo>) bar.Foos;
evil.Add(...);

sau đó chắc chắn, sẽ không có hại gì nếu bạn trả lại bộ sưu tập trực tiếp. Tôi thường cố gắng để có một chút hoang tưởng hơn thế.

Tương tự như vậy, như bạn nói: nếu bạn chỉ cần IEnumerable<T> , thì tại sao lại buộc mình vào bất cứ điều gì mạnh mẽ hơn?

Câu trả lời gốc

Nếu bạn đang sử dụng .NET 3.5, bạn có thể tránh tạo một bản sao tránh việc truyền đơn giản bằng cách sử dụng một cuộc gọi đơn giản để Bỏ qua:

public IEnumerable<Foo> Foos {
    get { return foos.Skip(0); }
}

(Có rất nhiều tùy chọn khác để gói một cách tầm thường - điều tuyệt vời về SkipChọn / Trường hợp không có đại biểu nào thực hiện vô nghĩa cho mỗi lần lặp.)

Nếu bạn không sử dụng .NET 3.5, bạn có thể viết một trình bao bọc rất đơn giản để làm điều tương tự:

public static IEnumerable<T> Wrapper<T>(IEnumerable<T> source)
{
    foreach (T element in source)
    {
        yield return element;
    }
}

15
Lưu ý rằng có một hình phạt về hiệu suất cho việc này: AFAIK phương thức Enumerable.Count được tối ưu hóa cho Bộ sưu tập được đúc thành IEnumerables, nhưng không dành cho phạm vi được tạo ra bởi Skip (0)
shojtsy

6
-1 Có phải chỉ mình tôi hay đây là một câu trả lời cho một câu hỏi khác? Thật hữu ích khi biết "giải pháp" này, nhưng op đã yêu cầu so sánh giữa việc trả lại ReadOnlyCollection và IEnumerable. Câu trả lời này đã cho rằng bạn muốn trả lại IEnumerable mà không có bất kỳ lý do nào để hỗ trợ cho quyết định đó.
Zaid Masud

1
Câu trả lời cho thấy truyền một ReadOnlyCollectionđến một ICollectionvà sau đó chạy Addthành công. Theo tôi biết, điều đó là không thể. Các evil.Add(...)nên ném ra một lỗi. dotnetfiddle.net/GII2jW
Shaun Luttin

1
@ShaunLuttin: Vâng, chính xác - đó là ném. Nhưng trong câu trả lời của tôi, tôi không chọn một ReadOnlyCollection- Tôi đã chọn một IEnumerable<T>thứ không thực sự chỉ đọc ... nó thay vì phơi bàyReadOnlyCollection
Jon Skeet

1
Aha. Chứng hoang tưởng của bạn sẽ dẫn bạn sử dụng ReadOnlyCollectionthay vì một IEnumerable, để bảo vệ chống lại sự đúc ác.
Shaun Luttin

43

Nếu bạn chỉ cần lặp qua bộ sưu tập:

foreach (Foo f in bar.Foos)

sau đó trả lại IEnumerable là đủ.

Nếu bạn cần truy cập ngẫu nhiên vào các mục:

Foo f = bar.Foos[17];

sau đó bọc nó trong ReadOnlyCollection .


@Vojislav Stojkovic, +1. Lời khuyên này có còn được áp dụng cho đến ngày hôm nay không?
w0051977

1
@ w0051977 Nó phụ thuộc vào ý định của bạn. Sử dụng System.Collections.Immutablenhư Jon Skeet khuyến nghị là hoàn toàn hợp lệ khi bạn phơi bày thứ gì đó sẽ không bao giờ thay đổi sau khi bạn có được một tài liệu tham khảo về nó. Mặt khác, nếu bạn phơi bày một cái nhìn chỉ đọc về một bộ sưu tập có thể thay đổi, thì lời khuyên này vẫn có hiệu lực, mặc dù tôi có thể sẽ phơi bày nó dưới dạng IReadOnlyCollection(không tồn tại khi tôi viết bài này).
Vojislav Stojkovic

@VojislavStojkovic bạn không thể sử dụng bar.Foos[17]với IReadOnlyCollection. Sự khác biệt duy nhất là Counttài sản bổ sung . public interface IReadOnlyCollection<out T> : IEnumerable<T>, IEnumerable
Konrad

@Konrad Phải, nếu bạn cần bar.Foos[17], bạn phải phơi bày nó như ReadOnlyCollection<Foo>. Nếu bạn muốn biết kích thước và lặp qua nó, hãy phơi bày nó dưới dạng IReadOnlyCollection<Foo>. Nếu bạn không cần biết kích thước, hãy sử dụng IEnumerable<Foo>. Có những giải pháp khác và các chi tiết khác, nhưng nó vượt quá phạm vi thảo luận của câu trả lời cụ thể này.
Vojislav Stojkovic

31

Nếu bạn làm điều này thì không có gì ngăn người gọi của bạn chuyển IEnumerable trở lại ICollection và sau đó sửa đổi nó. ReadOnlyCollection loại bỏ khả năng này, mặc dù vẫn có thể truy cập vào bộ sưu tập có thể ghi bên dưới thông qua sự phản chiếu. Nếu bộ sưu tập nhỏ thì cách an toàn và dễ dàng để khắc phục vấn đề này là trả lại một bản sao thay thế.


14
Đó là kiểu "thiết kế hoang tưởng" mà tôi thực sự không đồng ý. Tôi nghĩ rằng đó là cách thực hành tồi để chọn ngữ nghĩa không đầy đủ để thực thi chính sách.
Vojislav Stojkovic

24
Bạn không đồng ý không làm cho nó sai. Nếu tôi đang thiết kế một thư viện để phân phối bên ngoài, tôi muốn có một giao diện hoang tưởng hơn là xử lý các lỗi do người dùng cố gắng lạm dụng API. Xem hướng dẫn thiết kế C # tại msdn.microsoft.com/en-us/l
Library / k2604h5s (VS.71) .aspx

5
Có, trong trường hợp cụ thể của việc thiết kế thư viện để phân phối bên ngoài, sẽ có một giao diện hoang tưởng cho bất cứ điều gì bạn phơi bày. Mặc dù vậy, nếu ngữ nghĩa của thuộc tính Foos là truy cập tuần tự, hãy sử dụng ReadOnlyCollection và sau đó trả về IEnumerable. Không cần sử dụng sai ngữ nghĩa;)
Vojislav Stojkovic

9
Bạn cũng không cần phải làm. Bạn có thể trả về một trình vòng lặp chỉ đọc mà không cần sao chép ...
Jon Skeet

1
@shojtsy Dòng mã của bạn dường như không hoạt động. như tạo ra một null . Một diễn viên ném một ngoại lệ.
Nick Alexeev

3

Tôi tránh sử dụng ReadOnlyCollection càng nhiều càng tốt, nó thực sự chậm hơn đáng kể so với việc chỉ sử dụng Danh sách bình thường. Xem ví dụ này:

List<int> intList = new List<int>();
        //Use a ReadOnlyCollection around the List
        System.Collections.ObjectModel.ReadOnlyCollection<int> mValue = new System.Collections.ObjectModel.ReadOnlyCollection<int>(intList);

        for (int i = 0; i < 100000000; i++)
        {
            intList.Add(i);
        }
        long result = 0;

        //Use normal foreach on the ReadOnlyCollection
        TimeSpan lStart = new TimeSpan(System.DateTime.Now.Ticks);
        foreach (int i in mValue)
            result += i;
        TimeSpan lEnd = new TimeSpan(System.DateTime.Now.Ticks);
        MessageBox.Show("Speed(ms): " + (lEnd.TotalMilliseconds - lStart.TotalMilliseconds).ToString());
        MessageBox.Show("Result: " + result.ToString());

        //use <list>.ForEach
        lStart = new TimeSpan(System.DateTime.Now.Ticks);
        result = 0;
        intList.ForEach(delegate(int i) { result += i; });
        lEnd = new TimeSpan(System.DateTime.Now.Ticks);
        MessageBox.Show("Speed(ms): " + (lEnd.TotalMilliseconds - lStart.TotalMilliseconds).ToString());
        MessageBox.Show("Result: " + result.ToString());

15
Tối ưu hóa sớm. Theo số đo của tôi, việc lặp lại trên ReadOnlyCollection mất khoảng 36% so với danh sách thông thường, giả sử bạn không làm trong vòng lặp for. Rất có thể, bạn sẽ không bao giờ nhận thấy sự khác biệt này, nhưng nếu đây là một điểm nóng trong mã của bạn và yêu cầu từng chút hiệu suất cuối cùng bạn có thể vắt kiệt nó, tại sao không sử dụng Mảng thay thế? Điều đó sẽ nhanh hơn vẫn còn.
StriplingWar Warrior

Điểm chuẩn của bạn chứa sai sót, bạn hoàn toàn bỏ qua dự đoán chi nhánh.
Trojan

0

Đôi khi bạn có thể muốn sử dụng một giao diện, có lẽ vì bạn muốn chế nhạo bộ sưu tập trong quá trình kiểm tra đơn vị. Vui lòng xem mục blog của tôi để thêm giao diện của riêng bạn vào ReadonlyCollection bằng cách sử dụng bộ chuyển đổi.

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.