Truy cập vào biến foreach trong cảnh báo đóng cửa


85

Tôi nhận được cảnh báo sau:

Truy cập vào biến foreach khi đóng. Có thể có các hành vi khác nhau khi được biên dịch với các phiên bản trình biên dịch khác nhau.

Đây là những gì nó trông giống như trong trình chỉnh sửa của tôi:

thông báo lỗi đã đề cập ở trên trong cửa sổ bật lên khi di chuột

Tôi biết cách khắc phục cảnh báo này, nhưng tôi muốn biết tại sao tôi lại nhận được cảnh báo này?

Đây có phải là về phiên bản "CLR"? Nó có liên quan đến "IL" không?



1
TL; DR câu trả lời: thêm ToList () hoặc .ToArray () ở phần cuối của biểu thức truy vấn của bạn và nó sẽ thoát khỏi cảnh báo
— JoelFan

Câu trả lời:


136

Có hai phần cho cảnh báo này. Đầu tiên là...

Quyền truy cập vào biến foreach khi đóng

... mà không phải là không hợp lệ nhưng thoạt nhìn nó phản trực quan. Nó cũng rất khó để làm đúng. (Nhiều đến mức bài báo tôi liên kết đến dưới đây mô tả điều này là "có hại".)

Thực hiện truy vấn của bạn, lưu ý rằng mã bạn đã trích về cơ bản là một dạng mở rộng của những gì trình biên dịch C # (trước C # 5) tạo ra cho foreach1 :

Tôi [không] hiểu tại sao [điều sau] không hợp lệ:

string s; while (enumerator.MoveNext()) { s = enumerator.Current; ...

Vâng, nó là hợp lệ về mặt cú pháp. Và nếu tất cả những gì bạn đang làm trong vòng lặp của mình đang sử dụng giá trị của sthì mọi thứ đều tốt. Nhưng đóng cửa ssẽ dẫn đến hành vi phản trực giác. Hãy xem đoạn mã sau:

var countingActions = new List<Action>();

var numbers = from n in Enumerable.Range(1, 5)
              select n.ToString(CultureInfo.InvariantCulture);

using (var enumerator = numbers.GetEnumerator())
{
    string s;

    while (enumerator.MoveNext())
    {
        s = enumerator.Current;

        Console.WriteLine("Creating an action where s == {0}", s);
        Action action = () => Console.WriteLine("s == {0}", s);

        countingActions.Add(action);
    }
}

Nếu bạn chạy mã này, bạn sẽ nhận được đầu ra bảng điều khiển sau:

Creating an action where s == 1
Creating an action where s == 2
Creating an action where s == 3
Creating an action where s == 4
Creating an action where s == 5

Đây là những gì bạn mong đợi.

Để xem thứ mà bạn có thể không mong đợi, hãy chạy đoạn mã sau ngay sau đoạn mã trên:

foreach (var action in countingActions)
    action();

Bạn sẽ nhận được đầu ra bảng điều khiển sau:

s == 5
s == 5
s == 5
s == 5
s == 5

Tại sao? Bởi vì chúng tôi đã tạo ra năm hàm đều hoạt động giống nhau: in giá trị của s(mà chúng tôi đã đóng lại). Trên thực tế, chúng có cùng chức năng ("In s", "In s", "In s" ...).

Tại thời điểm chúng tôi sử dụng chúng, chúng thực hiện chính xác những gì chúng tôi yêu cầu: in giá trị của s. Nếu bạn nhìn vào giá trị đã biết cuối cùng của s, bạn sẽ thấy rằng đó là 5. Vì vậy, chúng tôi được s == 5in năm lần vào bảng điều khiển.

Đó chính xác là những gì chúng tôi yêu cầu, nhưng có lẽ không phải là những gì chúng tôi muốn.

Phần thứ hai của cảnh báo ...

Có thể có các hành vi khác nhau khi được biên dịch với các phiên bản trình biên dịch khác nhau.

... là những gì nó được. Bắt đầu với C # 5, trình biên dịch tạo mã khác "ngăn" điều này xảy ra thông quaforeach .

Do đó, đoạn mã sau sẽ tạo ra các kết quả khác nhau trong các phiên bản khác nhau của trình biên dịch:

foreach (var n in numbers)
{
    Action action = () => Console.WriteLine("n == {0}", n);
    countingActions.Add(action);
}

Do đó, nó cũng sẽ tạo ra cảnh báo R # :)

Đoạn mã đầu tiên của tôi, ở trên, sẽ thể hiện cùng một hành vi trong tất cả các phiên bản của trình biên dịch, vì tôi không sử dụng foreach(đúng hơn, tôi đã mở rộng nó theo cách mà các trình biên dịch trước C # 5 làm).

Đây có phải là phiên bản CLR không?

Tôi không chắc bạn đang hỏi gì ở đây.

Bài đăng của Eric Lippert nói rằng sự thay đổi xảy ra "trong C # 5". Vì thếcó lẽ bạn phải nhắm mục tiêu .NET 4.5 trở lên với trình biên dịch C # 5 trở lên để nhận hành vi mới và mọi thứ trước đó có hành vi cũ.

Nhưng nói rõ, đó là một chức năng của trình biên dịch chứ không phải phiên bản .NET Framework.

Có liên quan với IL không?

Các mã khác nhau tạo ra IL khác nhau nên theo nghĩa đó, có hậu quả đối với IL được tạo ra.

1 foreach là một cấu trúc phổ biến hơn nhiều so với mã bạn đã đăng trong nhận xét của mình. Vấn đề thường phát sinh thông qua việc sử dụng foreach, không phải thông qua cách liệt kê thủ công. Đó là lý do tại sao những thay đổi foreachtrong C # 5 giúp ngăn chặn vấn đề này, nhưng không hoàn toàn.


7
Tôi đã thực sự thử vòng lặp foreach trên các trình biên dịch khác nhau nhận được các kết quả khác nhau bằng cách sử dụng cùng một mục tiêu (.Net 3.5). Tôi đã sử dụng VS2010 (lần lượt sử dụng trình biên dịch được liên kết với .net 4.0 mà tôi tin tưởng) và VS2012 (tôi tin rằng trình biên dịch .net 4.5). Về nguyên tắc, điều này có nghĩa là nếu bạn đang sử dụng VS2013 và chỉnh sửa một dự án nhắm mục tiêu .Net 3.5 và xây dựng nó trên một máy chủ bản dựng có cài đặt một khuôn khổ cũ hơn một chút, bạn có thể thấy các kết quả khác nhau của chương trình trên máy của mình so với bản dựng đã triển khai.
— Ykok

Câu trả lời tốt, nhưng không chắc "foreach" có liên quan như thế nào. Điều này sẽ không xảy ra với cách liệt kê thủ công hay thậm chí là một vòng lặp for (int i = 0; i <collection.Size; i ++) đơn giản? Có vẻ như đó là một vấn đề với việc bao đóng đi ra ngoài phạm vi, hay chính xác hơn, một vấn đề với việc mọi người hiểu cách đóng hoạt động khi họ đi ra khỏi phạm vi mà họ đã được xác định bên trong.
— Brad

Những foreachthứ ở đây đến từ nội dung của câu hỏi. Bạn nói đúng rằng nó có thể xảy ra theo nhiều cách khác nhau, tổng quát hơn.
— ta.speot.is

1
Tại sao R # vẫn cảnh báo tôi, nó không đọc khung mục tiêu, mà tôi đã đặt thành 4,5.
— Johnny_D

1
"Vì vậy, có lẽ bạn phải nhắm mục tiêu .NET 4.5 trở lên" Tuyên bố này không đúng. Phiên bản .NET mà bạn nhắm mục tiêu không ảnh hưởng đến điều này, hành vi cũng được thay đổi trong .NET 2.0, 3.5 và 4 nếu bạn đang sử dụng C # 5 (VS 2012 hoặc mới hơn) để biên dịch. Đó là lý do tại sao bạn chỉ nhận được cảnh báo này trên .NET 4.0 trở xuống, nếu bạn nhắm mục tiêu 4.5, bạn không nhận được cảnh báo vì bạn không thể biên dịch 4.5 trên trình biên dịch C # 4 trở lên.
— Scott Chamberlain

12

Câu trả lời đầu tiên là tuyệt vời, vì vậy tôi nghĩ tôi chỉ cần thêm một điều.

Bạn nhận được cảnh báo bởi vì, trong mã ví dụ của bạn, ReflectModel đang được gán IEnumerable, sẽ chỉ được đánh giá tại thời điểm liệt kê và bản thân việc liệt kê có thể xảy ra bên ngoài vòng lặp nếu bạn đã gán phản xạModel cho thứ gì đó có phạm vi rộng hơn .

Nếu bạn đã thay đổi

...Where(x => x.Name == property.Value)

đến

...Where(x => x.Name == property.Value).ToList()

sau đó, mirrorModel sẽ được gán một danh sách xác định trong vòng lặp foreach, vì vậy bạn sẽ không nhận được cảnh báo, vì việc liệt kê chắc chắn sẽ xảy ra trong vòng lặp chứ không phải bên ngoài nó.


Tôi đã đọc rất nhiều lời giải thích thực sự dài mà không giải quyết được vấn đề này cho tôi, sau đó một bài giải thích ngắn gọn đã giải quyết được vấn đề đó. cảm ơn!
— Charles Clayton

Tôi đã đọc câu trả lời được chấp nhận và chỉ nghĩ "làm thế nào nó là một bao đóng nếu nó không ràng buộc các biến?" nhưng bây giờ tôi hiểu đó là khi đánh giá xảy ra, cảm ơn!
— Jerome

Vâng, đây là giải pháp phổ quát rõ ràng. Chậm, tốn nhiều trí nhớ, nhưng tôi nghĩ nó thực sự hoạt động 100% cho mọi trường hợp.
— Al Kepp

8

Một biến phạm vi khối sẽ giải quyết cảnh báo.

foreach (var entry in entries)
{
   var en = entry; 
   var result = DoSomeAction(o => o.Action(en));
}
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 cookie và Chính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.