2010-02-09 22 views
20

Tôi đến từ một nền tảng lập trình chức năng tại thời điểm này, vì vậy hãy tha thứ cho tôi nếu tôi không hiểu đóng cửa trong C#.Đóng cửa trong các đại biểu xử lý sự kiện của C#?

Tôi có đoạn code sau đây để tự động tạo Buttons rằng có được xử lý sự kiện nặc danh:

for (int i = 0; i < 7; i++) 
{ 
    Button newButton = new Button(); 

    newButton.Text = "Click me!"; 

    newButton.Click += delegate(Object sender, EventArgs e) 
    { 
     MessageBox.Show("I am button number " + i); 
    }; 

    this.Controls.Add(newButton); 
} 

tôi mong đợi các văn bản "I am button number " + i phải đóng cửa với giá trị của i ở đó lặp đi lặp lại của vòng lặp for. Tuy nhiên, khi tôi thực sự chạy chương trình, mọi Nút nói I am button number 7. Tôi đang thiếu gì? Tôi đang sử dụng VS2005.

Chỉnh sửa: Vì vậy, tôi đoán câu hỏi tiếp theo của tôi là, làm cách nào để nắm bắt giá trị?

+4

Bạn không nắm bắt được giá trị. Bạn không bao giờ nắm bắt các giá trị, chỉ các biến. Để biết thêm thông tin về vấn đề này, hãy xem http://blogs.msdn.com/ericlippert/archive/2009/11/12/closing-over-the-loop-variable-considered-harmful.aspx và http: //blogs.msdn .com/ericlippert/archive/2009/11/16/đóng-over-the-loop-biến-part-two.aspx –

Trả lời

26

Để có được hành vi này, bạn cần phải sao chép các biến cục bộ, không sử dụng iterator:

for (int i = 0; i < 7; i++) 
{ 
    var inneri = i; 
    Button newButton = new Button(); 
    newButton.Text = "Click me!"; 
    newButton.Click += delegate(Object sender, EventArgs e) 
    { 
     MessageBox.Show("I am button number " + inneri); 
    }; 
    this.Controls.Add(newButton); 
} 

Lý do được thảo luận chi tiết hơn nhiều in this question.

4

Việc đóng cửa chụp biến không phải là giá trị. Điều này có nghĩa là vào thời điểm ủy nhiệm được thực thi, tức là sau khi kết thúc vòng lặp, giá trị của i là 6.

Để ghi lại giá trị, gán nó cho một biến được khai báo trong thân vòng lặp. Trên mỗi lần lặp của vòng lặp, một cá thể mới sẽ được tạo cho mỗi biến được khai báo bên trong nó.

Jon Skeet's articles on closures có giải thích sâu hơn và nhiều ví dụ hơn.

for (int i = 0; i < 7; i++) 
{ 
    var copy = i; 

    Button newButton = new Button(); 

    newButton.Text = "Click me!"; 

    newButton.Click += delegate(Object sender, EventArgs e) 
    { 
     MessageBox.Show("I am button number " + copy); 
    }; 

    this.Controls.Add(newButton); 
} 
+0

-1: một câu trả lời nhiều thông tin giải thích, sâu hơn, những gì đang xảy ra với một ví dụ nên được xếp hạng cao hơn. – IAbstract

1

Bởi thời gian bạn nhấp vào bất kỳ nút, họ đều được tạo 1-7, vì vậy tất cả họ sẽ thể hiện trạng thái cuối cùng của tôi là 7.

4

Bạn đã tạo Bảy đại biểu, nhưng mỗi đại biểu nắm giữ tham chiếu đến cùng một trường hợp của i.

Chức năng MessageBox.Show chỉ được gọi là khi nút được nhấp. Vào thời điểm nút đã nhấp, vòng lặp đã hoàn tất. Vì vậy, tại thời điểm này, i sẽ bằng bảy.

Hãy thử điều này:

for (int i = 0; i < 7; i++) 
{ 

    Button newButton = new Button(); 

    newButton.Text = "Click me!"; 

    int iCopy = i; // There will be a new instance of this created each iteration 
    newButton.Click += delegate(Object sender, EventArgs e) 
    { 
     MessageBox.Show("I am button number " + iCopy); 
    }; 

    this.Controls.Add(newButton); 
} 
23

Nick có điều đó đúng, nhưng tôi muốn giải thích một chút tốt hơn trong văn bản của câu hỏi này một cách chính xác tại sao .

Vấn đề không phải là đóng cửa; đó là for-loop. Vòng lặp chỉ tạo một biến "i" cho toàn bộ vòng lặp. Nó không tạo ra một biến mới "i" cho mỗi lần lặp. Lưu ý: này được báo cáo thay đổi cho C# 5.

Điều này có nghĩa khi đại biểu vô danh của bạn chụp hoặc đóng qua rằng "i" biến nó đóng cửa hơn một biến mà được chia sẻ bởi tất cả các nút. Bởi thời gian bạn thực sự nhận được để bấm vào bất kỳ các nút đó vòng lặp đã hoàn thành incrementing biến đó lên đến 7.

Một trong những điều tôi có thể làm cách khác nhau từ mã của Nick là sử dụng một chuỗi cho biến bên trong và xây dựng tất cả những chuỗi lên phía trước chứ không phải là lúc nút bấm, như vậy:

for (int i = 0; i < 7; i++) 
{ 
    var message = string.Format("I am button number {0}.", i); 

    Button newButton = new Button(); 
    newButton.Text = "Click me!"; 
    newButton.Click += delegate(Object sender, EventArgs e) 
    { 
     MessageBox.Show(message); 
    }; 
    this.Controls.Add(newButton); 
} 

Đó chỉ nghề một chút bộ nhớ (giữ cho các biến chuỗi lớn hơn thay vì số nguyên) cho một chút thời gian cpu sau này ... nó phụ thuộc vào ứng dụng của bạn những gì quan trọng hơn.

lựa chọn khác là không tự mã hóa vòng lặp tại tất cả:

this.Controls.AddRange(Enumerable.Range(0,7).Select(i => 
{ 
    var b = new Button() {Text = "Click me!", Top = i * 20}; 
    b.Click += (s,e) => MessageBox.Show(string.Format("I am button number {0}.", i)); 
    return b; 
}).ToArray()); 

Tôi thích Tùy chọn cuối cùng này không hẳn vì nó loại bỏ các vòng lặp nhưng vì nó bắt đầu bạn suy nghĩ về việc xây dựng này điều khiển từ một nguồn dữ liệu.

+0

+1 để cải thiện hơn nữa! –

+0

Đây không phải là một lỗi nhưng họ đang thay đổi nó. Giống như Silverlight là một khuôn khổ khả thi (nhưng có thể không bao giờ nhận được bất kỳ tính năng mới nào và đã giảm/hỗ trợ về hưu). – micahhoover

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