2010-05-07 6 views
13

Tôi có hai tập hợp các chuỗi: CollectionA là một thuộc tính StringCollection của một đối tượng được lưu trữ trong hệ thống, trong khi CollectionB là một Danh sách được tạo ra trong thời gian chạy. Bộ sưu tập cần được cập nhật để phù hợp với CollectionB nếu có bất kỳ sự khác biệt nào. Vì vậy, tôi nghĩ ra những gì tôi mong đợi là một phương pháp LINQ đơn giản để thực hiện việc loại bỏ.Tại sao tôi nhận được "Bộ sưu tập đã được sửa đổi; hoạt động điều tra có thể không thực thi" khi không sửa đổi bộ sưu tập được liệt kê?

var strDifferences = CollectionA.Where(foo => !CollectionB.Contains(foo)); 
foreach (var strVar in strDifferences) { CollectionA.Remove(strVar); } 

Nhưng tôi nhận được lỗi "Collection was modified; enumeration operation may not execute" trên strDifferences ... mặc dù đó là số đếm riêng từ bộ sưu tập đang được sửa đổi! Ban đầu tôi đã phát minh ra điều này rõ ràng để tránh lỗi này, vì triển khai đầu tiên của tôi sẽ tạo ra nó (như tôi đã liệt kê trên CollectionA và chỉ xóa khi !CollectionB.Contains(str)). Bất cứ ai có thể khoe một số cái nhìn sâu sắc vào lý do tại sao điều tra này là không?

Trả lời

21

Hai không hoàn toàn tách biệt. Where không tạo bản sao riêng của bộ sưu tập. Nó trong nội bộ giữ một tham chiếu đến bộ sưu tập ban đầu và lấy các phần tử từ nó khi bạn yêu cầu chúng.

Bạn có thể giải quyết sự cố của mình bằng cách thêm ToList() để buộc Where lặp lại qua bộ sưu tập ngay lập tức.

var strDifferences = CollectionA 
    .Where(foo => !CollectionB.Contains(foo)) 
    .ToList(); 
+0

Sẽ không 'ToArray()' tốt hơn ở đây? Không cần sử dụng 'List'. – svick

+0

@svick Đó là điều tôi đã tự hỏi trong một thời gian, nhưng đó là một câu hỏi tôi muốn hỏi vào một ngày khác. Với điều kiện nó chưa được yêu cầu, tất nhiên là -uckle- –

+1

@GraceNote bắt được ở đây: http://stackoverflow.com/questions/1105990/is-it-better-to-call-tolist-or-toarray-in -linq-queries – nawfal

3

Where họ sẽ trả lại IEnumerable<T> và sau đó bạn đang sử dụng mà trong bạn foreach (var strVar in strDifferences)

Sau đó bạn đang cố gắng để xoá bỏ nó từ bộ sưu tập đã tạo ra IEnumerable<T>. Bạn chưa tạo danh sách mới, nó tham chiếu CollectionA để kéo mục tiếp theo từ, vì vậy bạn không thể chỉnh sửa CollectionA.

Bạn có thể làm điều này cũng:

var strDifferences = CollectionA.Where 
    (foo => CollectionB.Contains(foo)).ToList(); 

CollectionA = strDifferences; 
//or instead of reassigning CollectionA 
CollectionA.Clear(); 
CollectionA.AddRange(strDifferences); 

Vì bạn đang loại bỏ những người không nằm trong CollectionB. Chỉ cần tìm những cái đang có, tạo một danh sách và gán danh sách đó vào biến CollectionA.

+0

Ah ... Tôi rất thích sử dụng lựa chọn thay thế của bạn (tuy nhiên nó có hiệu lực giống hệt nhau), nhưng tiếc là CollectionA, hóa ra, là thuộc tính chỉ đọc, vì vậy tôi không thể đặt nó. Tuy nhiên, bạn đưa ra một lời giải thích sâu hơn nhiều, vì vậy +1 cho điều đó. –

+0

@ccornet nếu không thể đặt nó, bạn có thể xóa nó và thêm các mục trở lại. – kemiller2002

+0

Tôi nghĩ về nó lâu và khó. Tôi quyết định vẫn đi với phương pháp loại bỏ. Tôi không muốn sửa đổi CollectionA nếu không có thay đổi là cần thiết, xây dựng một danh sách những gì cần phải được loại bỏ làm cho điều này rất nhanh chóng và đơn giản để biết (chỉ cần xem nếu strDifferences.Count> 0). Nếu tôi xây dựng một mảng mục tiêu, tôi vẫn phải kiểm tra xem CollectionA có thứ gì cần được xóa hay không. –

3

Hãy thử thay đổi dòng đầu tiên một chút:

var strDifferences = 
    CollectionA.Where(foo => !CollectionB.Contains(foo)).ToList(); 

Tôi nghĩ LINQ đang sử dụng thực hiện lười biếng của truy vấn của bạn. Cuộc gọi đến ToList sẽ bắt buộc thực hiện truy vấn trước khi liệt kê.

+0

Có, và đảm bảo không đặt nó trên hai dòng i..e. var strDifferencesTemp = Bộ sưu tậpA.Where (foo =>! CollectionB.Contains (foo)); var strDifferences = strDifferencesTemp.ToList(); nếu bạn làm như vậy, bộ sưu tập có thể được sửa đổi bởi một số chuỗi khác giữa hai dòng này. – Markus