1. Giới thiệu
Trong C# (và .NET nói chung) events — là một trong những cách để các đối tượng giao tiếp với nhau. Ví dụ, bạn có một nút bấm, khi nhấn nút đó phải khởi chạy một việc gì đó. Có thể cài xử lý sự kiện bằng cách khai báo một phương thức riêng, rồi đăng ký phương thức đó vào sự kiện... Nhưng thường bạn muốn "mô tả phản ứng ngay ở chỗ này", không rải logic ra khắp file.
Biểu thức lambda giải quyết vấn đề này rất tốt — cho phép bạn viết phản ứng với sự kiện ngay tại chỗ đăng ký. Tối thiểu thủ tục — tối đa hiệu quả.
Điều tương tự áp dụng cho callback: đôi khi bạn truyền vào một phương thức một logic để thực thi "sau này", ví dụ khi một thao tác hoàn thành, và một lần nữa — lambda làm cho việc đó trở nên đơn giản và trực quan: toàn bộ logic cần thiết nằm ngay chỗ gọi.
Sự kiện và callback: tóm tắt chính và khi nào dùng lambda
Sự kiện — không gì khác hơn là "bảng thông báo" trên một đối tượng: "ở đây ai đó có thể đăng ký và làm gì đó khi có chuyện xảy ra".
Callback (gọi lại) — là "tôi cho bạn một hàm bây giờ, bạn gọi nó khi cần". Rất giống với "để lại yêu cầu — chúng tôi sẽ gọi lại".
Khi bạn đã học về delegate và events trước đó, bạn đã thấy cú pháp kiểu như:
button.Click += MyButtonClickHandler;
Ở đó MyButtonClickHandler — là một phương thức đặc biệt cần khai báo ở đâu đó. Nhưng khi logic đơn giản, bạn muốn gọn trong vài dòng, không tạo thêm phương thức chỉ để làm một việc.
Đó là lúc lambda xuất hiện:
button.Click += (sender, args) => { Console.WriteLine("Nút đã được nhấn!"); };
Ngắn gọn, rõ ràng, ngay tại chỗ!
2. Lambda như handler sự kiện: từ đơn giản đến thú vị
Hình thức đơn giản: bóng đèn chớp — in gì đó ra console
Giả sử ta có lớp Button, và ta muốn phản ứng với sự kiện Click của nó.
// Hãy tưởng tượng một nút như sau:
public class Button
{
public event EventHandler? Click;
public void SimulateClick() // Ví dụ: "bấm" bằng chương trình
{
// Nếu ai đó đã đăng ký — gọi các handler
Click?.Invoke(this, EventArgs.Empty);
}
}
Bây giờ dùng cái nút này:
var button = new Button();
// Đăng ký sự kiện của nút bằng lambda!
button.Click += (sender, args) =>
{
Console.WriteLine("Yeah! Nút đã được nhấn!");
};
button.SimulateClick(); // => Yeah! Nút đã được nhấn!
Giải thích:
— Mọi thứ sau => — là thân của "mini-hàm" của bạn, sẽ được gọi khi sự kiện xảy ra.
— Không cần lo vòng đời của handler: nó sống bằng việc bạn đăng ký (+=). Quan trọng: nếu lambda capture biến từ bên ngoài và bạn không hủy đăng ký sự kiện, điều này có thể dẫn tới leak bộ nhớ.
Lambda handler với capture biến
int clickCount = 0;
button.Click += (sender, args) =>
{
clickCount++;
Console.WriteLine($"Nút đã được nhấn {clickCount} lần!");
};
button.SimulateClick(); // => Nút đã được nhấn 1 lần!
button.SimulateClick(); // => Nút đã được nhấn 2 lần!
Chuyện gì đang xảy ra:
Lambda "capture" biến clickCount từ bên ngoài và nhớ giá trị của nó giữa các lần gọi.
3. Ứng dụng biểu thức lambda cho callbacks
Callback thường dùng khi một đoạn mã cần gọi hàm của bạn sau — ví dụ sau khi thực hiện một công việc dài.
Truyền lambda như callback
Giả sử có một phương thức nhận delegate hoặc lambda làm tham số:
void DoWork(Action callback)
{
Console.WriteLine("Bắt đầu công việc...");
// Giả lập công việc
System.Threading.Thread.Sleep(500); // (Đừng làm thế trong ứng dụng thực tế, đây là thao tác blocking!)
callback(); // Sau khi xong gọi callback
}
DoWork(() => Console.WriteLine("Công việc hoàn thành!"));
Kết quả:
Trước tiên console sẽ in "Bắt đầu công việc...", sau đó "Công việc hoàn thành!".
Bạn có thể truyền lambda có tham số:
void Calculate(int a, int b, Action<int> onResult)
{
int sum = a + b;
onResult(sum);
}
Calculate(5, 8, result => Console.WriteLine($"Kết quả: {result}"));
4. Lỗi thường gặp khi dùng lambda
Không hủy đăng ký sự kiện
Khi bạn đăng ký sự kiện bằng lambda, bạn không có "tên handler" để dễ hủy đăng ký sau này:
button.Click += (s, e) => Console.WriteLine("...");
button.Click -= ??? // Làm sao truyền lambda vào đây? Không thể, nếu bạn không lưu nó.
Nếu cần hủy đăng ký — lưu lambda vào biến:
EventHandler handler = (s, e) => Console.WriteLine("...");
button.Click += handler;
// ...sau đó
button.Click -= handler;
Điều này quan trọng với đối tượng sống lâu: nếu không hủy đăng ký, tham chiếu sẽ còn và có thể gây leak bộ nhớ.
Capture biến trong vòng lặp
Kinh điển: đăng ký sự kiện trong vòng lặp:
for (int i = 0; i < 3; i++)
button.Click += (s, e) => Console.WriteLine(i); // Tất cả lambda "nhớ" cùng một i
Trong các phiên bản C# hiện đại hành vi này được sửa cho foreach, nhưng với for đôi khi có khác biệt — luôn cẩn thận với biến bị capture!
Nếu muốn mỗi lambda nhớ giá trị riêng — tạo bản sao cục bộ:
for (int i = 0; i < 3; i++)
{
int localI = i;
button.Click += (s, e) => Console.WriteLine(localI);
}
GO TO FULL VERSION