1. Cú pháp đăng ký sự kiện
Hãy tưởng tượng một ứng dụng quản lý task: khi task hoàn thành thì phát sinh một sự kiện, và handler có thể, ví dụ, gửi thông báo, cập nhật UI hoặc ghi log. C# làm việc với sự kiện tiện và an toàn: bạn rõ ràng chỉ ra handler nào phản ứng với sự kiện nào.
Đăng ký sự kiện trong C# — giống như tự thêm mình vào danh sách khách mời ở một bữa tiệc:
publisher.MyEvent += HandlerMethod;
Ở đây publisher — đối tượng khai báo sự kiện MyEvent, còn HandlerMethod — phương thức sẽ được gọi khi sự kiện xảy ra.
Xem ví dụ tối thiểu. Giả sử chúng ta có app đếm số lần nhấn:
public class Clicker
{
public event Action Clicked;
public void Click()
{
// Có cái gì đó được click!
Clicked?.Invoke();
}
}
Đây là "publisher" của sự kiện Clicked. Giờ ta đăng ký một handler:
Clicker clicker = new Clicker();
void OnClicked()
{
Console.WriteLine("Nút đã được nhấn!");
}
clicker.Clicked += OnClicked;
// Ở đâu đó trong code
clicker.Click();
// → "Nút đã được nhấn!"
Nó hoạt động thế nào? Chúng ta thêm phương thức OnClicked vào sự kiện "click", và phương thức này sẽ chạy mỗi khi có click.
2. Event handlers: loại và cách khai báo
Handler của sự kiện là phương thức được gọi khi sự kiện xảy ra. Signature của handler phải khớp với kiểu delegate của sự kiện. Ví dụ, nếu sự kiện khai báo là public event Action Clicked;, thì handler phải là phương thức không có tham số và không trả về giá trị.
Handler cho Action
void OnClicked()
{
Console.WriteLine("Sự kiện đã xảy ra (Action)!");
}
Handler cho chuẩn EventHandler
Khi dùng cách truyền thống với EventHandler, handler nhận hai tham số: sender (object sender) và dữ liệu sự kiện (EventArgs e):
public class Alarm
{
public event EventHandler AlarmRaised;
public void RaiseAlarm()
{
AlarmRaised?.Invoke(this, EventArgs.Empty);
}
}
Alarm alarm = new Alarm();
void AlarmHandler(object sender, EventArgs e)
{
Console.WriteLine("Báo động đã được kích hoạt!");
}
alarm.AlarmRaised += AlarmHandler;
alarm.RaiseAlarm();
Anonymous methods và lambda expressions
C# cho phép dùng anonymous functions và lambda expressions trực tiếp khi đăng ký:
clicker.Clicked += () => Console.WriteLine("Another click!");
Hoặc phức tạp hơn nếu sự kiện có arguments:
alarm.AlarmRaised += (sender, e) =>
{
Console.WriteLine($"Alarm raised by: {sender}");
};
4. Những điểm cần lưu ý hữu ích
Đăng ký và hủy đăng ký: các chi tiết quan trọng
Cuộc sống thật là khi khách ngày càng đông hoặc ai đó muốn về nhà. Với delegates cũng vậy: bạn có thể add (đăng ký) và remove (hủy đăng ký) handlers:
// Đăng ký
publisher.MyEvent += MyHandler;
// Hủy đăng ký (khi handler không cần nữa)
publisher.MyEvent -= MyHandler;
Tại sao quan trọng? Nếu không hủy đăng ký, đặc biệt trong app lớn, handlers có thể "treo" và không cho garbage collector dọn dẹp object — dẫn tới memory leaks.
Nhiều handlers
Bạn có thể đăng ký bao nhiêu handler tùy ý cho một sự kiện. Chúng sẽ được gọi theo thứ tự đăng ký.
clicker.Clicked += () => Console.WriteLine("Handler đầu tiên!");
clicker.Clicked += () => Console.WriteLine("Handler thứ hai!");
clicker.Click();
// → Handler đầu tiên!
// → Handler thứ hai!
Bạn thậm chí có thể đăng ký/hủy đăng ký handlers "on the fly" — rất linh hoạt và tiện.
Tại sao điều này quan trọng: các kịch bản thực tế
- UI (Windows Forms, WPF, WinUI, MAUI): click nút, thay đổi text trong field — tất cả là sự kiện.
- FileSystemWatcher: thông báo khi có file mới trong thư mục.
- Asynchronous operations: hoàn thành download file, tiến độ của task.
- Plugin system: các module riêng đăng ký vào sự kiện của app chính.
Mô hình event giúp xây dựng kiến trúc mở rộng: bạn có thể thêm module mới và đăng ký với các sự kiện đã có mà không chỉnh code nền.
Ví dụ đăng ký
| Signature của sự kiện | Ví dụ đăng ký | Ví dụ handler |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
5. Lỗi thường gặp và cạm bẫy
Lỗi #1: signature handler không khớp.
Nếu sự kiện khai báo là event Action<int> nhưng bạn cố đăng ký phương thức không có tham số, compiler sẽ báo lỗi. Luôn kiểm tra phương thức có khớp signature của sự kiện không.
Lỗi #2: capture biến trong lambda.
Khi đăng ký bằng lambda expression, nó có thể capture biến từ scope bên ngoài (xem chủ đề "closures"). Nếu sau khi đăng ký biến đó thay đổi, handler sẽ thấy giá trị mới — có thể gây hành vi bất ngờ.
Lỗi #3: đăng ký vào sự kiện của object chưa khởi tạo.
Nếu bạn đăng ký trước khi object chứa sự kiện được tạo và khởi tạo, bạn có thể gặp NullReferenceException. Trước khi đăng ký hãy chắc object đã sẵn sàng.
Lỗi #4: đăng ký handler cùng một handler nhiều lần.
Nếu cùng một handler được đăng ký nhiều lần, nó sẽ được gọi tương ứng nhiều lần. Đây không phải bug mà là đặc tính của event, nhưng thường gây bất ngờ.
GO TO FULL VERSION