2012-01-13 26 views
5

Tôi đã phương pháp sau:Tại sao C# không bảo toàn ngữ cảnh cho một cuộc gọi đại biểu ẩn danh?

static Random rr = new Random(); 
static void DoAction(Action a) 
{ 
    ThreadPool.QueueUserWorkItem(par => 
    { 
     Thread.Sleep(rr.Next(200)); 
     a.Invoke(); 
    }); 
} 

bây giờ tôi gọi đây là trong một vòng lặp for như thế này:

for (int i = 0; i < 10; i++) 
{ 
    var x = i; 

    DoAction(() => 
    { 
     Console.WriteLine(i); // scenario 1 
     //Console.WriteLine(x); // scenario 2 
    }); 
} 

trong kịch bản 1 đầu ra là: 10 10 10 10 ... 10
trong kịch bản 2 đầu ra là: 2 6 5 8 4 ... 0 (hoán vị ngẫu nhiên từ 0 đến 9)

Bạn giải thích điều này như thế nào? C# không được bảo tồn các biến (ở đây i) cho cuộc gọi đại biểu ẩn danh?

+2

Nhưng bảo toàn ('chụp') biến * * i là * chính xác * điều gì đang xảy ra! Những gì * không * xảy ra, mặc dù bạn muốn nó, được giữ lại * giá trị ** ** của 'i' tại thời điểm đại biểu được thiết lập *. – AakashM

+2

Ngẫu nhiên, ReSharper sẽ cảnh báo bạn về [Truy cập để đóng cửa sửa đổi] (http://confluence.jetbrains.net/display/ReSharper/Access+to+modified+closure) khi được trình bày với mã này; bạn có thể tìm thấy lời giải thích hữu ích. – AakashM

+0

http://blogs.msdn.com/b/ericlippert/archive/2009/11/12/closing-over-the-loop-variable-considered-harmful.aspx –

Trả lời

10

Vấn đề ở đây là có i biến và mười bản sao/bản sao của x. Mỗi lambda được tham chiếu đến biến đơn i và một trong các trường hợp của x. Mỗi x chỉ được viết một lần và do đó mỗi lambda nhìn thấy một giá trị được ghi vào giá trị mà nó tham chiếu.

Biến i được ghi vào cho đến khi nó đạt đến 10 Không ai trong số các lambdas chạy cho đến khi vòng lặp hoàn thành nên tất cả họ đều nhìn thấy giá trị cuối cùng của i đó là 10

tôi thấy ví dụ này là một chút rõ ràng hơn nếu bạn viết lại nó như sau

int i = 0; // Single i for every iteration of the loop 
while (i < 10) { 
    int x = i; // New x for every iteration of the loop 
    DoAction(() => { 
    Console.WriteLine(i); 
    Console.WriteLine(x); 
    }); 
    i++; 
}; 
+1

Cụ thể hơn, mỗi 'x' không chỉ được viết chỉ một lần, nhưng là một 'x' khác vì nó được khởi tạo mỗi lần. Có 10 'x' trong vòng đời của vòng lặp và chỉ có một' i'. –

1

Tôi nghĩ bạn sẽ nhận được kết quả tương tự với java hoặc bất kỳ Ngôn ngữ hướng đối tượng nào (không chắc chắn nhưng ở đây có vẻ hợp lý).

Phạm vi của i là cho toàn bộ vòng lặp và phạm vi của x là cho mỗi lần xuất hiện.

Tính năng chia sẻ lại giúp bạn phát hiện ra loại vấn đề này hàng đầu.

1

DoAction tạo chuỗi và trả về ngay lập tức. Khi chủ đề được đánh thức từ giấc ngủ ngẫu nhiên của nó, vòng lặp sẽ được hoàn thành và giá trị của i sẽ nâng cấp lên đến 10. Giá trị của x, mặt khác, được chụp và đóng băng trước khi gọi , vì vậy bạn sẽ nhận được tất cả các giá trị từ 0 đến 9 theo thứ tự ngẫu nhiên, tùy thuộc vào thời gian mỗi luồng được ngủ dựa trên trình tạo số ngẫu nhiên của bạn.

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