2012-03-05 38 views
6

Tôi không thể hiểu cách lặp qua danh sách Action. Khi tôi thử nó, tôi kết thúc với các giá trị giống như lần lặp trước.Lặp qua danh sách các hành động

Dưới đây là mã (ví dụ đơn giản):

string[] strings = { "abc", "def", "ghi" }; 

var actions = new List<Action>(); 
foreach (string str in strings) 
    actions.Add(new Action(() => { Trace.WriteLine(str); })); 

foreach (var action in actions) 
    action(); 

Output:

ghi 
ghi 
ghi 

Tại sao nó luôn chọn các yếu tố cuối cùng trong strings khi nó thực hiện các hành động?
Và làm thế nào tôi có thể đạt được kết quả mong muốn đó sẽ là:

abc 
def 
ghi 

Trả lời

13

hành động của bạn là một đóng cửa, do đó nó truy cập str chính nó, không phải là một bản sao của str:

foreach (string str in strings) 
{ 
    var copy = str; // this will do the job 
    actions.Add(new Action(() => { Trace.WriteLine(copy); })); 
} 
+0

Gah, bạn thắng. Tôi biết cách sửa nó, nhưng tôi không thể nhớ lý do tại sao. Đóng cửa! Tôi cần đóng cửa! +1 :) – Joshua

+1

@Joshua không lâu lắm rồi khi tôi học được một chút sâu hơn :) ... điều này có thể tốt cho việc đọc thêm http://stackoverflow.com/questions/9412672/lambda-expressions-with -multithreading-in-c-sharp –

+0

Thú vị, tôi chưa bao giờ nhận ra. Cảm ơn. – demoncodemonkey

3

Đây là một khá tình hình phức tạp. Câu trả lời ngắn gọn là tạo bản sao của biến cục bộ trước khi gán cho bản đóng:

string copy = str; 
actions.Add(new Action(() => { Trace.WriteLine(copy); })); 

Check out this article on closures để biết thêm thông tin.

3

Hành vi này là điều kiện bởi Closures.

Biến số hiện diện trong lambda của bạn là tham chiếukhông phải là giá trị sao chép. Điều đó có nghĩa là các điểm với giá trị mới nhất được giả định bởi str, là "ghi" trong trường hợp của bạn. Đó là lý do tại sao cho mỗi cuộc gọi, nó chỉ đi đến vị trí bộ nhớ cùng một vị trí bộ nhớ và phục hồi, tự nhiên, cùng một giá trị.

Nếu bạn viết mã, như trong câu trả lời được cung cấp, bạn buộc một trình biên dịch C# để tái tạo một giá trị mới mỗi thời gian, do đó, một mới địa chỉ sẽ được chuyển đến các labmda, vì vậy mỗi lambda sẽ có nó riêng biến số.

Nhân tiện, nếu tôi không nhầm lẫn, C# đội hứa sẽ sửa lỗi này hành vi không tự nhiên trong C# 5.0. Vì vậy, tốt hơn nên kiểm tra blog của họ về chủ đề này để biết các cập nhật trong tương lai.

+2

+1 giải thích tốt. Có thể đáng nói đến là Java làm theo cách khác. Nếu bạn xử lý với 'Runnable' (thường) để bắt đầu một luồng, các biến được * sao chép * vào ngữ cảnh mới. Đây cũng là lý do tại sao Java buộc bạn phải làm cho chúng 'final'. –

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