Event trong C#

Nói chung, một event (sự kiện) là một cái gì đó đặc biệt sẽ xảy ra. Ví dụ: Microsoft ra mắt sự kiện //build/ cho các nhà phát triển, để giúp họ biết về các sản phẩm mới hoặc các tính năng mới của sản phẩm hiện có.

Microsoft thông báo cho các nhà phát triển về sự kiện này qua email hoặc các tùy chọn quảng cáo khác. Vì vậy, trong trường hợp này, Microsoft là nhà xuất bản đã phát hành (raise) một sự kiện (event) và thông báo cho các nhà phát triển về nó và các nhà phát triển là người đăng ký của sự kiện và tham dự (handle) sự kiện.

Các event trong C# tuân theo một khái niệm tương tự. Một sự kiện có nhà xuất bản, người đăng ký, thông báo và xử lý.

Các thành phần điều khiển của giao diện đồ họa người dùng (GUI) thường sử dụng sự kiện để xử lý các thao tác của người dùng. Ví dụ: một nút nhấn trong Windows Forms có nhiều sự kiện như nhấp chuột, di chuột, v.v ...

Một lớp tùy chỉnh cũng có thể có event để thông báo cho các lớp khác về điều gì đó đã hoặc sắp xảy ra. Hãy xem cách bạn có thể định nghĩa một sự kiện và thông báo cho các lớp khác có trình xử lý sự kiện.

Một sự kiện không có gì ngoài một delegate được đóng gói. Như chúng ta đã tìm hiểu ở phần trước, delegate là kiểu dữ liệu tham chiếu. Bạn có thể khai báo đại biểu như hình dưới đây:

public delegate void EventHandler();

Nếu bạn chưa xem qua hướng dẫn về delegate trong C# thì có thể xem ở đây:

Delegate trong C# | Comdy
Delegate trong C# là gì? Delegate trong C# dùng để làm gì? Cách sử dụng Delegate trong C#.

Bây giờ, để khai báo một event, hãy sử dụng từ khóa event trước khi khai báo một biến kiểu delegate như sau:

public delegate void EventHandler();

public event EventHandler handler;

Bây giờ, hãy xem một ví dụ thực tế của một event. Hãy xem xét lớp PrintHelper sau đây in các số nguyên với những định dạng khác nhau như số, tiền, thập phân, nhiệt độ và thập lục phân.

Nó có một sự kiện BeforePrintEvent để thông báo cho những lớp đã đăng ký về sự kiện BeforePrint trước khi nó thực hiện in.

public class PrintHelper
{
    // declare delegate 
    public delegate void BeforePrint();
    
    //declare event of type delegate
    public event BeforePrint BeforePrintEvent;
      
    public PrintHelper()
    { }

    public void PrintNumber(int num)
    {
        RaiseEvent();
        Console.WriteLine("Number: {0,-12:N0}", num);
    }

    public void PrintDecimal(int dec)
    {
        RaiseEvent();
        Console.WriteLine("Decimal: {0:G}", dec);
    }

    public void PrintMoney(int money)
    {
        RaiseEvent();
        Console.WriteLine("Money: {0:C}", money);
    }

    public void PrintTemperature(int num)
    {
        RaiseEvent();
        Console.WriteLine("Temperature: {0,4:N1} F", num);
    }
    
    public void PrintHexadecimal(int dec)
    {
        RaiseEvent();
        Console.WriteLine("Hexadecimal: {0:X}", dec);
    }
    
    private void RaiseEvent()
    {
        if (BeforePrintEvent != null)
        {
            BeforePrintEvent();
        }
    }
}

Lớp PrintHelper là một nhà xuất bản xuất bản sự kiện BeforePrint. Lưu ý rằng trong mỗi phương thức in, đầu tiên nó sẽ gọi phương thức RaiseEvent(), phương thức này sẽ kiểm tra xem BeforePrintEvent không phải là null và sau đó nó gọi thực thi BeforePrintEvent(). BeforePrintEvent là một đối tượng của delegate có kiểu BeforePrint, vì vậy nó sẽ null nếu không có lớp nào đăng ký sự kiện và đó là lý do tại sao cần phải kiểm tra null trước khi gọi một delegate.

Tips: Delegate cũng có thể được gọi bằng phương thức Invoke (), ví dụ: beforePrintEvent.Invoke ().

Bây giờ, hãy tạo một lớp đăng ký sự kiện. Hãy xem xét lớp Number đơn giản sau đây chẳng hạn.

class Number
{
    private int _value;
    private PrintHelper _printHelper;

    public Number(int val)
    {
        _value = val;           
        _printHelper = new PrintHelper();
        //subscribe to beforePrintEvent event
        _printHelper.BeforePrintEvent += BeforePrintEventHandler;
    }
    
    //BeforePrintEvent handler
    void BeforePrintEventHandler()
    {
        Console.WriteLine("PrintHelper is going to print a value");
    }

    public int Value
    {
        get { return _value; }
        set { _value = value; }
    }

    public void PrintMoney()
    {
        _printHelper.PrintMoney(_value);
    }

    public void PrintNumber()
    {
        _printHelper.PrintNumber(_value);
    }
}

Tất cả các lớp đăng ký phải cung cấp chức năng xử lý sự kiện, chức năng này sẽ được gọi khi nhà xuất bản phát sinh sự kiện. Trong ví dụ trên, lớp Number khởi tạo một thể hiện của lớp PrintHelper và đăng ký sự kiện BeforePrintEvent sử dụng toán tử += và cung cấp tên của phương thức sẽ xử lý sự kiện (nó sẽ được gọi khi phát sinh sự kiện). Phương thức BeforePrintEventHandler là trình xử lý sự kiện có cùng chữ ký với delegate BeforePrint trong lớp PrintHelper.

Bây giờ, chúng ta tạo một thể hiện của lớp Number và gọi các phương thức in:

Number myNumber = new Number(100000);
myNumber.PrintMoney();
myNumber.PrintNumber();

Đây là kết quả khi biên dịch và chạy chương trình:

PrintHelper is going to print value
Money: $ 1,00,000.00
PrintHelper is going to print value
Number: 1,00,000

Hình ảnh sau đây minh họa một mô hình xử lý sự kiện trong C#:

Mô hình xử lý sự kiện trong C#

Đối số của event

Các event cũng có thể truyền dữ liệu dưới dạng đối số cho trình xử lý sự kiện. Một sự kiện chuyển các đối số cho trình xử lý sự kiện theo chữ ký của delegate.

Trong ví dụ sau, PrintHelper định nghĩa delegate BeforePrint chấp nhận một đối số string. Bây giờ, bạn có thể truyền một chuỗi bất kỳ cho trình xử lý sự kiện khi bạn phát sinh một sự kiện.

public class PrintHelper
{
    public delegate void BeforePrint(string message);
    public event BeforePrint BeforePrintEvent;

    public void PrintNumber(int num)
    {
        RaiseEvent("PrintNumber");
        Console.WriteLine("Number: {0,-12:N0}", num);
    }

    public void PrintDecimal(int dec)
    {
        RaiseEvent("PrintDecimal");
        Console.WriteLine("Decimal: {0:G}", dec);
    }

    public void PrintMoney(int money)
    {
        RaiseEvent("PrintMoney");
        Console.WriteLine("Money: {0:C}", money);
    }

    public void PrintTemperature(int num)
    {
        RaiseEvent("PrintTemerature");
        Console.WriteLine("Temperature: {0,4:N1} F", num);
    }
    
    public void PrintHexadecimal(int dec)
    {
        RaiseEvent("PrintHexadecimal");
        Console.WriteLine("Hexadecimal: {0:X}", dec);
    }
    
    private void RaiseEvent(string message)
    {
        if (BeforePrintEvent != null)
        {
            BeforePrintEvent(message);
        }
    }
}

Bây giờ, lớp đăng ký sự kiện cần phải chỉnh sửa một chút ở trình xử lý sự kiện để có cùng chữ ký với delegate BeforePrint - bổ sung một đối số kiểu string. Khi sự kiện xảy ra, một chuỗi sẽ được gửi tới trình xử lý sự kiện, chúng ta sẽ in chuỗi này ra.

class Number
{
    private int _value;
    private PrintHelper _printHelper;

    public Number(int val)
    {
        _value = val;           
        _printHelper = new PrintHelper();
        //subscribe to beforePrintEvent event
        _printHelper.BeforePrintEvent += BeforePrintEventHandler;
    }
    
    //beforePrintevent handler
    void BeforePrintEventHandler(string message)
    {
        Console.WriteLine("BeforePrintEvent fires from {0}", message);
    }

    public int Value
    {
        get { return _value; }
        set { _value = value; }
    }

    public void PrintMoney()
    {
        _printHelper.PrintMoney(_value);
    }

    public void PrintNumber()
    {
        _printHelper.PrintNumber(_value);
    }

}

Đây là kết quả khi biên dịch và chạy chương trình:

BeforePrintEvent fires from PrintMoney.
Money: $ 1,00,000.00
BeforePrintEvent fires from PrintNumber.
Number: 1,00,000

Tham khảo thêm về delegate và event.

Những điểm cần nhớ:

  • Sử dụng từ khóa event với kiểu dữ liệu delegate để khai báo một sự kiện.
  • Kiểm tra sự kiện có null hay không trước khi đưa ra một sự kiện.
  • Đăng ký sự kiện bằng cách sử dụng toán tử +=. Hủy đăng ký sự kiện sử dụng toán tử -=.
  • Chức năng xử lý sự kiện được gọi là trình xử lý sự kiện. Trình xử lý sự kiện phải có cùng chữ ký với delegate của event.
  • Event có thể có các đối số được dùng để chuyển dữ liệu đến trình xử lý sự kiện.
  • Các sự kiện cũng có thể được khai báo static, virtual, sealed và abstract.
  • Một interface có thể thành viên là event.
  • Event sẽ không phát sinh nếu không có lớp đăng ký sự kiện.
  • Trình xử lý sự kiện được gọi đồng bộ nếu có nhiều lớp đăng ký sự kiện.
  • .NET framework sử dụng delegate EventHandler và lớp cơ sở EventArgs làm đối số để tạo event.


Bài viết liên quan:

Hướng dẫn này sẽ giúp bạn tìm hiểu về đọc ghi file (File I/O) trong C# và sử dụng các lớp tiện ích để đọc ghi file.

Reflection trong C#

  • 6 min read

Reflection trong C# là gì? Ứng dụng của Reflection trong C#. Cách khai báo và sử dụng Reflection trong C#.

Attribute trong C#

  • 7 min read

Attribute trong C# là gì? Có những loại attribute nào trong C#? Làm sao để sử dụng attribute trong C#.