Việc tạo một Danh sách mới để sửa đổi một bộ sưu tập trong mỗi vòng lặp có phải là một lỗ hổng thiết kế không?


13

Gần đây tôi đã chạy vào hoạt động không hợp lệ phổ biến này Collection was modifiedtrong C # và trong khi tôi hiểu nó đầy đủ, nó dường như là một vấn đề phổ biến như vậy (google, khoảng 300k kết quả!). Nhưng nó cũng có vẻ là một điều hợp lý và đơn giản để sửa đổi một danh sách trong khi bạn đi qua nó.

List<Book> myBooks = new List<Book>();

public void RemoveAllBooks(){
    foreach(Book book in myBooks){
         RemoveBook(book);
    }
}

RemoveBook(Book book){
    if(myBooks.Contains(book)){
         myBooks.Remove(book);
         if(OnBookEvent != null)
            OnBookEvent(this, new EventArgs("Removed"));
    }
}

Một số người sẽ tạo một danh sách khác để lặp lại, nhưng đây chỉ là vấn đề. Giải pháp thực sự là gì, hoặc vấn đề thiết kế thực tế ở đây là gì? Tất cả chúng ta dường như muốn làm điều này, nhưng nó có phải là một lỗ hổng thiết kế không?


Bạn có thể sử dụng một trình vòng lặp hoặc loại bỏ từ cuối danh sách.
ElDuderino

@ElDuderino msdn.microsoft.com/en-us/l Library / dscyy5s0.aspx . Đừng bỏ lỡ You consume an iterator from client code by using a For Each…Next (Visual Basic) or foreach (C#) statementdòng
SJuan76

Trong Java có Iterator(hoạt động như bạn mô tả) và ListIterator(hoạt động như bạn mong muốn). Điều này sẽ chỉ rằng để cung cấp chức năng lặp qua một loạt các bộ sưu tập các loại và hiện thực, Iteratorlà cực kỳ hạn chế (những gì sẽ xảy ra nếu bạn xóa một nút trong một cây cân bằng? Liệu next()có ý nghĩa nữa), với ListIteratorviệc mạnh hơn do có ít trường hợp sử dụng.
SJuan76

Nếu ) và các trình vòng lặp đầu ra (nghĩa là chỉ ghi, hữu ích hơn âm thanh).
Jules

Câu trả lời:


9

Việc tạo một Danh sách mới để sửa đổi một bộ sưu tập trong mỗi vòng lặp có phải là một lỗ hổng thiết kế không?

Câu trả lời ngắn gọn: không

Nói một cách đơn giản, bạn tạo ra hành vi không xác định , khi bạn lặp qua một bộ sưu tập và sửa đổi nó cùng một lúc. Hãy suy nghĩ về việc xóa các nextyếu tố trong một chuỗi. Điều gì sẽ xảy ra, nếu MoveNext()được gọi là?

Một điều tra viên vẫn còn hiệu lực miễn là bộ sưu tập vẫn không thay đổi. Nếu các thay đổi được thực hiện cho bộ sưu tập, chẳng hạn như thêm, sửa đổi hoặc xóa các phần tử, thì điều tra viên bị vô hiệu hóa không thể phục hồi và hành vi của nó không được xác định. Điều tra viên không có quyền truy cập độc quyền vào bộ sưu tập; do đó, việc liệt kê thông qua một bộ sưu tập về bản chất không phải là một quy trình an toàn luồng.

Nguồn: MSDN

Nhân tiện, bạn có thể rút ngắn RemoveAllBooksđơn giảnreturn new List<Book>()

Và để xóa một cuốn sách, tôi khuyên bạn nên trả lại bộ sưu tập đã lọc:

return books.Where(x => x.Author != "Bob").ToList();

Một triển khai kệ có thể sẽ như sau:

public class Shelf
{
    List<Book> books=new List<Book> {
        new Book ("Paul"),
        new Book ("Peter")
    };

    public IEnumerable<Book> getAllBooks(){
        foreach(Book b in books){
            yield return b;
        }
    }

    public void RemovePetersBooks(){
        books= books.Where(x=>x.Author!="Peter").ToList();
    }

    public void EmptyShelf(){
        books = new List<Book> ();
    }

    public Shelf ()
    {
    }
}

public static void Main (string[] args)
{
    Shelf s = new Shelf ();
    foreach (Book b in s.getAllBooks()) {
        Console.WriteLine (b.Author);
    }
    s.RemovePetersBooks ();

    foreach (Book b in s.getAllBooks()) {
        Console.WriteLine (b.Author);
    }
    s.EmptyShelf ();
    foreach (Book b in s.getAllBooks()) {
        Console.WriteLine (b.Author);
    }
}

Bạn mâu thuẫn với chính mình khi bạn trả lời có và sau đó cung cấp một ví dụ cũng tạo ra một danh sách mới. Khác hơn là tôi đồng ý.
Esben Skov Pedersen

1
@EsbenSkovPedersen bạn đã đúng: DI đã nghĩ đến việc sửa đổi như một lỗ hổng ...
Thomas Junk

6

Tạo một danh sách mới để sửa đổi nó là một giải pháp tốt cho vấn đề mà các trình vòng lặp hiện có của danh sách không thể tiếp tục đáng tin cậy sau khi thay đổi trừ khi họ biết danh sách đã thay đổi như thế nào.

Một giải pháp khác là thực hiện sửa đổi bằng cách sử dụng iterator thay vì thông qua giao diện danh sách. Điều này chỉ hoạt động nếu chỉ có thể có một trình vòng lặp - nó không giải quyết được vấn đề cho nhiều luồng lặp đồng thời trong danh sách, điều này (miễn là việc truy cập dữ liệu cũ có thể chấp nhận được) tạo ra một danh sách mới.

Một giải pháp thay thế mà những người triển khai khung có thể đã thực hiện là để danh sách theo dõi tất cả các trình lặp của nó và thông báo cho họ khi có thay đổi. Tuy nhiên, chi phí chung của phương pháp này rất cao - để nó hoạt động chung với nhiều luồng, tất cả các thao tác lặp sẽ cần khóa danh sách (để đảm bảo nó không thay đổi trong khi tiến hành hoạt động), sẽ tạo ra tất cả danh sách Hoạt động chậm. Ngoài ra còn có chi phí bộ nhớ không cần thiết, cộng với nó yêu cầu thời gian chạy để hỗ trợ các tham chiếu mềm và IIRC phiên bản đầu tiên của CLR thì không.

Từ một góc nhìn khác, sao chép danh sách để sửa đổi, cho phép bạn sử dụng LINQ để chỉ định sửa đổi, điều này thường dẫn đến mã rõ ràng hơn so với việc lặp trực tiếp qua danh sách, IMO.

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.