2009-01-16 5 views
18

Tôi đã học C#, và tôi đang cố gắng hiểu lambdas. Trong mẫu này dưới đây, nó in ra 10 mười lần.Làm thế nào để nói một hàm lambda để chụp một bản sao thay vì một tham chiếu trong C#?

class Program 
{ 
    delegate void Action(); 
    static void Main(string[] args) 
    { 
     List<Action> actions = new List<Action>(); 

     for (int i = 0; i < 10; ++i) 
      actions.Add(()=>Console.WriteLine(i)); 

     foreach (Action a in actions) 
      a(); 
    } 
} 

Rõ ràng, các lớp được tạo ra phía sau lambda được lưu trữ một tham chiếu hoặc con trỏ vào biến int i, và được gán một giá trị mới để tham chiếu cùng mỗi lần lặp loop. Có cách nào để buộc các lamda để lấy một bản sao thay vào đó, giống như cú pháp C++ 0x

[&](){ ... } // Capture by reference 

vs

[=](){ ... } // Capture copies 
+0

Bạn có thể muốn đọc [bài viết này] (http://csharpindepth.com/Articles/Chapter5/Closures.aspx), được viết bởi Jon Skeet rất riêng của chúng tôi. –

+0

có thể trùng lặp của [C# Captured Variable In Loop] (http://stackoverflow.com/questions/271440/c-sharp-captured-variable-in-loop) – nawfal

+0

Tôi thấy tò mò rằng hầu hết các câu trả lời cho câu hỏi này là giải thích ngữ nghĩa chụp hoàn toàn rõ ràng cho tác giả của câu hỏi trong khi chỉ một số đề cập đến giải pháp (bản sao tạm thời). Không ai đọc câu hỏi trước khi trả lời? – ghord

Trả lời

18

Trình biên dịch đang làm gì là kéo lambda của bạn và bất kỳ biến nào được lambda thu thập vào trình biên dịch tạo ra lớp lồng nhau.

Sau khi biên dịch ví dụ của bạn trông rất giống này:

class Program 
{ 
     delegate void Action(); 
     static void Main(string[] args) 
     { 
       List<Action> actions = new List<Action>(); 

       DisplayClass1 displayClass1 = new DisplayClass1(); 
       for (displayClass1.i = 0; displayClass1.i < 10; ++displayClass1.i) 
         actions.Add(new Action(displayClass1.Lambda)); 

       foreach (Action a in actions) 
         a(); 
     } 

     class DisplayClass1 
     { 
       int i; 
       void Lambda() 
       { 
         Console.WriteLine(i); 
       } 
     } 
} 

Bằng cách làm cho một bản sao trong vòng lặp for, trình biên dịch tạo ra các đối tượng mới trong mỗi lần lặp, như vậy:

for (int i = 0; i < 10; ++i) 
{ 
    DisplayClass1 displayClass1 = new DisplayClass1(); 
    displayClass1.i = i; 
    actions.Add(new Action(displayClass1.Lambda)); 
} 
7

Giải pháp duy nhất tôi đã có thể tìm thấy là làm cho bản sao cục bộ trước tiên:

for (int i = 0; i < 10; ++i) 
{ 
    int copy = i; 
    actions.Add(() => Console.WriteLine(copy)); 
} 

Nhưng tôi không hiểu tại sao việc đặt bản sao bên trong vòng lặp lại khác với chụp ảnh lambda i.

+9

Bởi vì khai báo của int là bên trong vòng lặp for, do đó, nó được tái tạo mỗi lần. Có 10 int khác nhau được đặt tên là "copy", trong đó chỉ có một int có tên "i", trong phạm vi được curried. – technophile

9

Giải pháp duy nhất là tạo bản sao và tham chiếu cục bộ trong lambda. Tất cả các biến trong C# (và VB.Net) khi được truy cập trong một đóng sẽ có ngữ nghĩa tham chiếu so với ngữ nghĩa sao chép/giá trị. Không có cách nào để thay đổi hành vi này bằng một trong hai ngôn ngữ.

Lưu ý: Nó không thực sự biên dịch làm tham chiếu. Trình biên dịch đưa biến vào lớp đóng và chuyển hướng truy cập "i" vào một trường "i" bên trong lớp đóng đã cho. Nó thường dễ dàng hơn để nghĩ về nó như là ngữ nghĩa tham khảo mặc dù.

1

Hãy nhớ rằng các biểu thức lambda thực sự chỉ là cú pháp đường cho các phương thức ẩn danh.

Điều đó đang được nói, những gì bạn đang thực sự tìm kiếm là cách các phương thức ẩn danh sử dụng các biến cục bộ trong phạm vi cha mẹ.

Đây là liên kết mô tả điều này. http://www.codeproject.com/KB/cs/InsideAnonymousMethods.aspx#4