Quyền truy cập vào Đóng cửa đã sửa đổi (2)


101

Đây là phần mở rộng của câu hỏi từ Quyền truy cập đến Đóng cửa đã sửa đổi . Tôi chỉ muốn xác minh xem những thứ sau có thực sự đủ an toàn để sử dụng trong sản xuất hay không.

List<string> lists = new List<string>();
//Code to retrieve lists from DB    
foreach (string list in lists)
{
    Button btn = new Button();
    btn.Click += new EventHandler(delegate { MessageBox.Show(list); });
}

Tôi chỉ chạy qua phần trên một lần cho mỗi lần khởi động. Bây giờ nó có vẻ hoạt động ổn. Như Jon đã đề cập về kết quả phản trực giác trong một số trường hợp. Vì vậy, những gì tôi cần để ý ở đây? Sẽ ổn nếu danh sách được chạy qua nhiều lần?


18
Xin chúc mừng, bạn hiện là một phần của tài liệu Resharper. confluence.jetbrains.net/display/ReSharper/…
Kongress

1
Điều này khá phức tạp nhưng giải thích ở trên đã làm rõ cho tôi: Điều này có vẻ đúng nhưng trên thực tế, chỉ giá trị cuối cùng của biến str sẽ được sử dụng bất cứ khi nào nhấp vào bất kỳ nút nào. Lý do cho điều này là foreach giải phóng vào vòng lặp while, nhưng biến lặp được định nghĩa bên ngoài vòng lặp này. Điều này có nghĩa là vào thời điểm bạn hiển thị hộp thông báo, giá trị của str có thể đã được lặp lại thành giá trị cuối cùng trong bộ sưu tập chuỗi.
DanielV

Câu trả lời:


159

Trước C # 5, bạn cần khai báo lại một biến bên trong foreach - nếu không, nó sẽ được chia sẻ và tất cả các trình xử lý của bạn sẽ sử dụng chuỗi cuối cùng:

foreach (string list in lists)
{
    string tmp = list;
    Button btn = new Button();
    btn.Click += new EventHandler(delegate { MessageBox.Show(tmp); });
}

Đáng chú ý, hãy lưu ý rằng từ C # 5 trở đi, điều này đã thay đổi, và cụ thể trong trường hợpforeach , bạn không cần phải làm điều này nữa: mã trong câu hỏi sẽ hoạt động như mong đợi.

Để hiển thị điều này không hoạt động nếu không có thay đổi này, hãy xem xét những điều sau:

string[] names = { "Fred", "Barney", "Betty", "Wilma" };
using (Form form = new Form())
{
    foreach (string name in names)
    {
        Button btn = new Button();
        btn.Text = name;
        btn.Click += delegate
        {
            MessageBox.Show(form, name);
        };
        btn.Dock = DockStyle.Top;
        form.Controls.Add(btn);
    }
    Application.Run(form);
}

Chạy phần trên trước C # 5 và mặc dù mỗi nút hiển thị một tên khác nhau, việc nhấp vào các nút sẽ hiển thị "Wilma" bốn lần.

Điều này là do thông số ngôn ngữ (ECMA 334 v4, 15.8.4) (trước C # 5) xác định:

foreach (V v in x) embedded-statement sau đó được mở rộng thành:

{
    E e = ((C)(x)).GetEnumerator();
    try {
        V v;
         while (e.MoveNext()) {
            v = (V)(T)e.Current;
             embedded-statement
        }
    }
    finally {
         // Dispose e
    }
}

Lưu ý rằng biến v(là của bạn list) được khai báo bên ngoài vòng lặp. Vì vậy, theo quy tắc của các biến được bắt, tất cả các lần lặp lại của danh sách sẽ chia sẻ người giữ biến được bắt.

Từ C # 5 trở đi, điều này được thay đổi: biến lặp ( v) nằm trong phạm vi vòng lặp. Tôi không có tham chiếu đặc tả, nhưng về cơ bản nó trở thành:

{
    E e = ((C)(x)).GetEnumerator();
    try {
        while (e.MoveNext()) {
            V v = (V)(T)e.Current;
            embedded-statement
        }
    }
    finally {
         // Dispose e
    }
}

Hủy đăng ký lại; nếu bạn chủ động muốn hủy đăng ký một trình xử lý ẩn danh, mẹo là nắm bắt chính trình xử lý:

EventHandler foo = delegate {...code...};
obj.SomeEvent += foo;
...
obj.SomeEvent -= foo;

Tương tự như vậy, nếu bạn muốn một trình xử lý sự kiện chỉ dùng một lần (chẳng hạn như Tải, v.v.):

EventHandler bar = null; // necessary for "definite assignment"
bar = delegate {
  // ... code
  obj.SomeEvent -= bar;
};
obj.SomeEvent += bar;

Điều này hiện đang tự hủy đăng ký ;-p


Nếu đúng như vậy, biến tạm thời sẽ ở trong bộ nhớ cho đến khi ứng dụng đóng lại, để phục vụ ủy quyền và sẽ không nên làm điều đó đối với các vòng lặp rất lớn nếu biến đó chiếm nhiều bộ nhớ. Tôi nói đúng chứ?
bị lỗi

1
Nó sẽ lưu lại trong bộ nhớ trong khoảng thời gian có những thứ (nút) với sự kiện. Có một cách để hủy đăng ký đại biểu chỉ một lần mà tôi sẽ thêm vào bài đăng.
Marc Gravell

2
Nhưng để đủ điều kiện theo quan điểm của bạn: vâng, các biến được nắm bắt thực sự có thể tăng phạm vi của một biến. Bạn cần phải cẩn thận để không chụp những thứ bạn không mong đợi ...
Marc Gravell

1
Bạn có thể vui lòng cập nhật câu trả lời của mình liên quan đến thay đổi trong thông số kỹ thuật C # 5.0 không? Chỉ để làm cho nó trở thành một tài liệu wiki tuyệt vời về vòng lặp foreach trong C #. Đã có một số câu trả lời hay về sự thay đổi trong trình biên dịch C # 5.0 xử lý các vòng lặp foreach bit.ly/WzBV3L , nhưng chúng không phải là tài nguyên giống wiki.
Ilya Ivanov

1
@Kos vâng, forkhông thay đổi trong 5.0
Marc Gravell
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 cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.