2010-07-02 36 views
21

Xét đoạn mã sau:Sử dụng biến lặp của vòng lặp foreach trong biểu thức lambda - tại sao không thành công?

public class MyClass 
{ 
    public delegate string PrintHelloType(string greeting); 


    public void Execute() 
    { 

     Type[] types = new Type[] { typeof(string), typeof(float), typeof(int)}; 
     List<PrintHelloType> helloMethods = new List<PrintHelloType>(); 

     foreach (var type in types) 
     { 
      var sayHello = 
       new PrintHelloType(greeting => SayGreetingToType(type, greeting)); 
      helloMethods.Add(sayHello); 
     } 

     foreach (var helloMethod in helloMethods) 
     { 
      Console.WriteLine(helloMethod("Hi")); 
     } 

    } 

    public string SayGreetingToType(Type type, string greetingText) 
    { 
     return greetingText + " " + type.Name; 
    } 

... 

} 

Sau khi gọi myClass.Execute(), mã in các phản ứng bất ngờ sau:

 
Hi Int32 
Hi Int32 
Hi Int32 

Rõ ràng, tôi mong chờ "Hi String", "Hi Single", "Hi Int32", nhưng dường như nó không phải là trường hợp. Tại sao phần tử cuối cùng của mảng được lặp lại đang được sử dụng trong tất cả 3 phương thức thay vì phương thức thích hợp?

Bạn sẽ viết lại mã để đạt được mục tiêu mong muốn như thế nào?

+0

Tôi thậm chí không đọc câu hỏi, nhưng từ tiêu đề, tôi biết câu trả lời là: http://lorgonblog.spaces.live.com/blog/cns!701679AD17B6D310!689.entry – Brian

+0

Câu hỏi biến được ghi hàng ngày xé đầu xấu xí của nó. – Marc

Trả lời

28

Chào mừng bạn đến với thế giới của việc đóng cửa và các biến bắt :)

Eric Lippert có một chiều sâu giải thích về hành vi này:

về cơ bản, đó là biến vòng lặp được ghi lại, không phải là giá trị của nó. Để có được những gì bạn nghĩ rằng bạn sẽ nhận được, làm được điều này:

foreach (var type in types) 
{ 
    var newType = type; 
    var sayHello = 
      new PrintHelloType(greeting => SayGreetingToType(newType, greeting)); 
    helloMethods.Add(sayHello); 
} 
+4

Đèn hiệu @Eric Lippert đã được thắp sáng. –

+3

Không có thần nhưng Anders, và Eric là tiên tri của mình :) – SWeko

+0

Tôi có thể thêm rằng điều này có thể nắm bắt ngay cả những người thông thạo trong việc đóng cửa bảo vệ - Lua, và có lẽ các ngôn ngữ khác, có 'loại' lexically bên trong vòng lặp dấu ngoặc đơn. Vì vậy, trong Lua bạn vẫn nắm bắt được biến, nhưng đó là một biến mới mỗi lần lặp. Đây là một cái gì đó khi lập trình ở Lua bạn sử dụng mọi lúc - nhưng trong nhiều năm lập trình của tôi trong C# tôi vẫn chưa viết một phương thức được hưởng lợi từ phạm vi 'type'-is-outside-of-the-brackets . Có ai không? – Mania

3

Bạn có thể sửa chữa nó bằng cách giới thiệu thêm biến:

... 
foreach (var type in types) 
     { 
      var t = type; 
      var sayHello = new PrintHelloType(greeting => SayGreetingToType(t, greeting)); 
      helloMethods.Add(sayHello); 
     } 
.... 
5

Như một lời giải thích ngắn gọn rằng ám chỉ đến đăng blog mà SWeko tham chiếu, lambda đang ghi lại biến số , không phải là giá trị . Trong vòng lặp foreach, biến số không phải là duy nhất trên mỗi lần lặp, cùng một biến được sử dụng trong suốt vòng lặp (điều này rõ ràng hơn khi bạn thấy mở rộng trình biên dịch thực hiện trên foreach tại thời gian biên dịch). Kết quả là, bạn đã nắm bắt cùng một biến trong mỗi lần lặp và biến (như của lần lặp cuối cùng) đề cập đến phần tử cuối cùng của tập hợp của bạn.

Cập nhật: Trong các phiên bản mới hơn của ngôn ngữ (bắt đầu bằng C# 5), biến vòng lặp được coi là mới với mỗi lần lặp, do đó đóng trên nó không tạo ra vấn đề tương tự như nó đã làm trong phiên bản cũ (C# 4 và trước đó).

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