CodeGym /Các khóa học /C# SELF /Tạo sự kiện bằng delegate ( e...

Tạo sự kiện bằng delegate ( eventdelegate)

C# SELF
Mức độ , Bài học
Có sẵn

1. Giới thiệu

Vậy khi chúng ta nói về "sự kiện" (event) trong bối cảnh C#, nghĩa là cơ chế cho phép thông báo an toàn cho một hoặc nhiều đối tượng rằng có điều gì đó đã xảy ra. Sự kiện không phải phép màu: dưới nắp là delegate (từ khóa delegate) với lớp bảo vệ thêm để tránh sử dụng sai.

Hãy tưởng tượng đăng ký kênh YouTube. Kênh (đối tượng sở hữu sự kiện) có nút "Subscribe" — bất kỳ người xem (đối tượng khác) nào cũng có thể nhấn và vào danh sách subscribers (các delegate xử lý). Khi tác giả up video mới (gọi sự kiện), chỉ subscribers nhận thông báo, và chỉ chủ kênh quyết định khi nào điều đó xảy ra. Chi tiết quan trọng: người xem có thể subscribe hoặc unsubscribe, nhưng không thể tự gửi thông báo — quyền đó chỉ thuộc về chủ kênh.

Một chút cú pháp chính thức


// Khai báo delegate, xác định "hình thức" của handler sự kiện
public delegate void SimpleEventHandler();

// Khai báo sự kiện dựa trên delegate
public event SimpleEventHandler SomethingHappened;

Đơn giản: event là từ khóa để khai báo sự kiện, và kiểu của sự kiện luôn được xác định bởi một delegate.

2. Ví dụ đầu tiên đơn giản: sự kiện "Nhấn nút!"

Bước 1. Định nghĩa delegate cho các handler của sự kiện


// Handler sự kiện: không trả về, không nhận tham số
public delegate void ButtonClickHandler();

Bước 2. Lớp có sự kiện


public class Button
{
    // Sự kiện: có thể đăng ký và hủy đăng ký, nhưng không thể gọi từ bên ngoài
    public event ButtonClickHandler Click;

    // Phương thức "nhấn nút"
    public void Press()
    {
        Console.WriteLine("Nút đã được nhấn!");
        // Gọi sự kiện: thông báo tất cả subscribers
        Click?.Invoke();
    }
}

Lưu ý: sự kiện được khai báo dựa trên delegate, và trong phương thức Press sự kiện được gọi bằng ?.Invoke(). Tại sao? Vì sự kiện có thể rỗng (chưa ai đăng ký), khi đó Click bằng null. Toán tử gọi an toàn đảm bảo các handler chỉ được gọi khi có subscribers.

Bước 3. Đăng ký sự kiện và chạy mã


// Ví dụ sử dụng
public class Program
{
    public static void Main()
    {
        var button = new Button();

        // Thêm "người nghe" (đăng ký sự kiện)
        button.Click += OnButtonClicked;
        button.Click += () => Console.WriteLine("Một handler nữa!");

        button.Press(); // Mô phỏng nhấn nút

        // Có thể hủy đăng ký sự kiện
        button.Click -= OnButtonClicked;
        button.Press();
    }

    // Handler sự kiện bình thường
    public static void OnButtonClicked()
    {
        Console.WriteLine("Handler: Nút đã được nhấn!");
    }
}

Lần nhấn đầu tiên cả hai handler chạy, lần thứ hai — chỉ có lambda chạy.

3. Khác biệt giữa event và delegate

Với delegate bạn có thể gán thẳng giá trị và thậm chí thay hoàn toàn danh sách subscribers — ai đó có thể viết thứ gì đó như button.Click = null và tất cả đăng ký trước đó sẽ "biến mất".

Với event thì khác — nó ràng buộc nghiêm ngặt vào đối tượng. Chỉ chủ lớp nơi sự kiện được khai báo mới có thể gọi trực tiếp nó (ví dụ Click() từ bên trong lớp). Mã khác chỉ có thể đăng ký bằng += hoặc hủy bằng -=, nhưng không thể "xóa sạch" tất cả cùng một lúc. Điều này bảo vệ tính đóng gói và không cho phép "phá" hệ thống đăng ký.

4. Chữ ký: kiểu delegate cho sự kiện, tham số

Truyền thống trong .NET là: handler sự kiện nhận hai tham số — object sender (ai gọi sự kiện) và đối số sự kiện (EventArgs). Nên dùng delegate EventHandler hoặc EventHandler<TEventArgs>.

Ví dụ: delegate có tham số


public delegate void ButtonClickHandler(object sender, EventArgs e);

EventArgs là lớp cơ sở để truyền thông tin thêm. Nếu cần nhiều dữ liệu hơn, tạo lớp kế thừa riêng.

Áp dụng cho nút của chúng ta


// Lớp đối số sự kiện
public class ButtonClickEventArgs : EventArgs
{
    public string UserName { get; }

    public ButtonClickEventArgs(string userName)
    {
        UserName = userName;
    }
}

public class Button
{
    public event EventHandler<ButtonClickEventArgs> Click;

    public void Press(string userName)
    {
        Console.WriteLine("Nút đã được nhấn!");
        Click?.Invoke(this, new ButtonClickEventArgs(userName));
    }
}

public static void Main()
{
    var button = new Button();
    button.Click += OnButtonClicked;

    button.Press("Vasiliy");
}

public static void OnButtonClicked(object sender, ButtonClickEventArgs e)
{
    Console.WriteLine($"Người dùng {e.UserName} đã nhấn nút!");
}

5. Sơ đồ trực quan: sự kiện hoạt động như thế nào


+-------------+
| Người dùng  |
+-------------+
      |
      v
+--------------+
|  Button.Press|
+--------------+
      |
      v
+-----------------+         +------------------------+
|  Gọi Click?     |----->---|   Subscriber 1         |
+-----------------+         +------------------------+
      |                     |   Thực hiện handler     |
      v                     +------------------------+
+-----------------+         +------------------------+
|   Nếu có thêm   |----->---|   Subscriber 2         |
+-----------------+         +------------------------+
      :                     |   Thực hiện handler     |
      v                     +------------------------+

Button.Press gọi sự kiện; các handler của subscribers được thực thi theo thứ tự.

6. Những lưu ý hữu ích

Hình thức sự kiện được khuyến nghị trong .NET

Dùng EventHandlerEventHandler<TEventArgs> để mã tương thích với thư viện và công cụ. Cách này làm cho sự kiện dễ mở rộng: bạn thêm thuộc tính mới vào EventArgs mà không phá hỏng subscribers.

Tài liệu: EventHandler, event.

Sự kiện vs delegate

Nếu bạn cần liên kết một thành phần với một phương thức cụ thể — delegate là đủ (delegate). Nếu bạn muốn nhiều phần của chương trình có thể đăng ký/hủy đăng ký bất cứ lúc nào — dùng sự kiện (event).

7. Lỗi phổ biến và tình huống khó xử khi tạo sự kiện

Lỗi #1: gọi sự kiện mà không kiểm tra null. Nếu sự kiện không có subscribers, gọi trực tiếp sẽ dẫn tới NullReferenceException. Dùng gọi an toàn:


Click?.Invoke(...);

Lỗi #2: cố gắng gọi sự kiện từ bên ngoài lớp. Sự kiện chỉ có thể được phát (gọi) bên trong lớp nơi nó được khai báo. Từ lớp khác trình biên dịch sẽ báo lỗi — điều này bảo vệ tính đóng gói.

Lỗi #3: đăng ký một handler nhiều lần. Nếu một handler được đăng ký nhiều lần, nó sẽ được gọi tương ứng với số lần đăng ký. Đây là đặc tính của cơ chế đăng ký. Cẩn thận tránh duplicate += và hủy đúng bằng -=.

Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION