2012-02-16 27 views
91

Nếu tôi sử dụng:Tại sao Danh sách <T> .ForEach cho phép danh sách của nó được sửa đổi?

var strings = new List<string> { "sample" }; 
foreach (string s in strings) 
{ 
    Console.WriteLine(s); 
    strings.Add(s + "!"); 
} 

các Add trong foreach ném một InvalidOperationException (Bộ sưu tập đã được sửa đổi; hoạt động liệt kê không thể thực hiện), mà tôi cân nhắc hợp lý, vì chúng ta đang kéo tấm thảm từ dưới chân chúng ta.

Tuy nhiên, nếu tôi sử dụng:

var strings = new List<string> { "sample" }; 
strings.ForEach(s => 
    { 
    Console.WriteLine(s); 
    strings.Add(s + "!"); 
    }); 

nó kịp bắn chính nó trong chân bằng vòng lặp cho đến khi nó ném một OutOfMemoryException.

Điều này đến như một điều bất ngờ đối với tôi, vì tôi luôn nghĩ rằng List.ForEach chỉ là một trình bao bọc cho foreach hoặc cho for.
Có ai có lời giải thích về cách thức và lý do hành vi này không?

(Inpired bởi ForEach loop for a Generic List repeated endlessly)

+7

Tôi đồng ý. Đây là - đáng ngờ. Tôi wusggest bạn đăng rằng trên microsoft kết nối và yêu cầu làm rõ. – TomTom

+4

"Điều này đến như là một bất ngờ đối với tôi, như tôi luôn luôn nghĩ rằng List.ForEach hoặc là chỉ là một wrapper cho' foreach' hoặc cho 'for'." Nó vẫn có thể sử dụng 'for'. Bạn có thể thực hiện cùng một hành động trong một vòng lặp 'for' và tạo ra cùng một kết quả OutOfMemoryException. –

+0

Điều này dựa trên câu hỏi của tôi: http://stackoverflow.com/q/9311272/132239, cảm ơn SWeko vì đã đi vào chi tiết của nó – Sypress

Trả lời

68

Nó bởi vì phương pháp ForEach không sử dụng các điều tra viên, nó vòng qua các mục với một vòng lặp for:

public void ForEach(Action<T> action) 
{ 
    if (action == null) 
    { 
     ThrowHelper.ThrowArgumentNullException(ExceptionArgument.match); 
    } 
    for (int i = 0; i < this._size; i++) 
    { 
     action(this._items[i]); 
    } 
} 

(đang thu được với JustDecompile)

Kể từ khi các điều tra viên không được sử dụng, nó không bao giờ kiểm tra nếu danh sách đã thay đổi và điều kiện kết thúc của vòng lặp for không bao giờ đạt được bởi vì _size được tăng lên ở mọi lần lặp lại.

+0

Vâng, nhưng cách tính '_size' được tính như thế nào? Nếu nó chỉ được tính toán trước thì nếu chỉ chạy một lần cho ví dụ của tôi. Nó rõ ràng là làm mới bằng cách nào đó. – SWeko

+7

Nó được làm mới tại phương thức Thêm -> this._items [this._size ++] = item; – Fabio

+1

@SWeko, nó không được tính toán, nó được cập nhật mỗi khi một mục được thêm vào hoặc bị loại bỏ. –

14

được thực hiện thông qua for bên trong, vì vậy nó không sử dụng điều tra viên và nó cho phép thay đổi bộ sưu tập.

6

Vì ForEach được kết nối với lớp List bên trong sử dụng vòng lặp for được gắn trực tiếp với các thành viên nội bộ của nó - bạn có thể xem bằng cách tải xuống mã nguồn cho khuôn khổ .NET.

http://referencesource.microsoft.com/netframework.aspx

Trong trường hợp như một vòng lặp foreach là lần đầu tiên và quan trọng nhất một tối ưu hóa trình biên dịch mà còn phải hoạt động chống lại bộ sưu tập như một người quan sát - vì vậy nếu bộ sưu tập được sửa đổi nó ném một ngoại lệ.

+0

Và để trả lời nhận xét về @Thomas bài về cách làm mới - các thành viên nội bộ được làm mới khi thêm được gọi là vậy tại sao nó có thể theo kịp với những thay đổi. Nếu bạn đã thực hiện một chèn, tại một chỉ số ít hơn một hiện tại, bạn sẽ không bao giờ hoạt động trên mục đó bởi vì nó đã được lặp qua mục đó. Nhưng kể từ khi bạn thêm vào cuối nó hoạt động. –

+1

Có, thay đổi dòng 'Add' bằng' strings.Insert (0, s + "!") 'Chỉ in ra 'mẫu'. Thật kỳ lạ là điều này không được đề cập trong tài liệu. – SWeko

+0

Vâng, tôi nghĩ rằng Microsoft nhận ra rằng nó chỉ là về không thể cung cấp mọi caveat tồn tại trong tài liệu của họ - vì vậy họ cung cấp mã nguồn của họ ngay bây giờ. Tôi thấy rằng một giải pháp tốt hơn một cách trung thực nhưng vấn đề duy nhất tôi đã tìm thấy là các sản phẩm như WF không được cập nhật nhanh chóng - mã nguồn WF 4.x vẫn không có sẵn. –

4

Chúng tôi biết về vấn đề này, đó là sự giám sát khi được viết ban đầu. Thật không may, chúng ta không thể thay đổi nó vì nó bây giờ sẽ ngăn chặn mã làm việc trước đây từ chạy:

 var list = new List<string>(); 
     list.Add("Foo"); 
     list.Add("Bar"); 

     list.ForEach((item) => 
     { 
      if(item=="Foo") 
       list.Remove(item); 
     }); 

Tính hữu ích của phương pháp này chính nó là đáng ngờ như Eric Lippert chỉ ra, vì vậy chúng tôi không bao gồm nó cho .NET cho các ứng dụng kiểu Metro (ví dụ: ứng dụng Windows 8).

David Kean (Đội BCL)

+1

Tôi thấy rằng đây sẽ là một sự thay đổi lớn, nhưng dù sao nó có thể thất bại theo những cách không rõ ràng, và đó không bao giờ là một điều tốt. Tôi không thể nhìn thấy một kịch bản mà sử dụng phương pháp ForEach là cao hơn một đơn giản cho (hoặc foreach nếu mangling của danh sách ban đầu là không cần thiết) – SWeko

Các vấn đề liên quan