C# Cơ bản .NET Core §1) Cài đặt, chương trình C# đầu tiên §2) Biến, kiểu dữ liệu và nhập/xuất §3) Toán tử số học và gán §4) So sánh, logic và lệnh if, switch §5) Vòng lặp for, while §6) Phương thức - Method §7) Phương thức - Delegate §8) Lớp - Class §9) Kiểu vô danh và dynamic §10) Biểu thức lambda §11) Event §12) Hàm hủy - Quá tải toán tử - thành viên tĩnh - indexer §13) Lớp lồng nhau - namespace §14) null và nullable §15) Mảng §16) Chuỗi ký tự §17) Tính kế thừa §18) Tính đa hình - abstract - interface §19) Struct và Enum §20) Ngoại lệ Exeption §21) IDisposable - using §22) File cơ bản §23) FileStream §24) Generic §25) Collection - List §26) SortedList §27) Queue / Stack §28) Linkedlist §29) Dictionary - HashSet §30) Phương thức mở rộng §31) ObservableCollection §32) LINQ §33) (Multithreading) async - bất đồng bộ §34) Type §35) Attribute Annotation §36) DI Dependency Injection §37) (Multithreading) Parallel §38) (Networking) HttpClient §39) (Networking) HttpMessageHandler §40) (Networking) HttpListener §41) (Networking) Tcp TcpListenerr/TcpClient §42) (ADO.NET) SqlConnection §43) (ADO.NET) SqlCommand §44) (EF Core) Tổng quan §45) (EF Core) Tạo Model §46) (Asp.net core) Hello World! §47) (Asp.net Core) Map - Request - Response §48) (Asp.net Core) IServiceCollection - MapWith §49) (Asp.net Core) Session - ISession §50) (Asp.net Core) Middleware §51) (Asp.net Core) Configuration
Lập trình C# Cơ bản

Event trong C#

Các sự kiện (Event) là cơ chế để một lớp (đối tượng của lớp) này thông báo đến lớp khác có điều gì đó mà lớp khác quan tâm xảy ra. Lớp mà từ đó gửi đi sự kiện gọi tên nó là publisher và các lớp nhận được sự kiện gọi là là các subsriber

Để làm được việc này nó hoạt động giống hệt cơ chế Delegate, và thực tế là trong .NET các Event xây dựng với nền tảng chính là Delegate, nên trước khi tìm hiểu Event hay vào tìm hiểu Delegate trước.

Vậy có vấn đề gì khi sử dụng Delegate mà phải thêm khái niệm Event, hãy tìm hiểu trường hợp sau

Vấn đề của Delegate và sự giải quyết của Event

Ví dụ dưới đây sẽ dùng delegate (đã biết ở phần trước) để xây dựng cơ chế để một lớp này đăng ký nhận sự kiện từ một lớp khác

public class Publisher
{
    public delegate void NotifyNews(object data);

    public NotifyNews event_news;

    public void Send()
    {
        event_news?.Invoke("Co tin moi");
    }
}

public class SubscriberA
{
    public void Sub(Publisher p)
    {
        p.event_news += ReceiverFromPublisher;
    }

    void ReceiverFromPublisher(object data)
    {
        Console.WriteLine("SubscriberA: " + data.ToString());
    }
}
public class SubscriberB
{
    public void Sub(Publisher p)
    {
        p.event_news = null;
        p.event_news += ReceiverFromPublisher;
    }

    void ReceiverFromPublisher(object data)
    {
        Console.WriteLine("SubscriberB: "+ data.ToString());
    }
}


class Program
{
    static void Main(string[] args)
    {
        Publisher p = new Publisher();
        SubscriberA sa = new SubscriberA();
        SubscriberB sb = new SubscriberB();

        sa.Sub(p);
        sb.Sub(p);

        p.Send();

    }
}

Chạy đoạn mã trên, kết quả là:

SubscriberB: Co tin moi

Phân tích vấn đề của đoạn mã trên:

Lớp Publisher xây dựng một delegate có tên NotifyNews và khai báo thuộc tính event_news triển khai nó, khi Publisher thi hành Send() nó sẽ thi hành delegate này và như vậy những đối tượng nào đăng ký vào delegate sẽ có cơ hội nhận thông tin mới từ Publisher

Hai lớp SubscriberASubscriberB tiến hành đăng ký phương thức ReceiverFromPublisher vào Delegate của Pushisher, và như vậy khi chạy code đã có kết quả như trên.

Tuy nhiên, nhìn vào phương thức public void Sub(Publisher p) của SubscriberBthì đoạn mã:

p.event_news = null;
//...

Nó đã gán event_news bằng null, có nghĩa là việc đăng ký của SubcriberA lúc trước bị loại bỏ bởi SubcriberB, dẫn tới chỉ có SubcriberB nhận được tin mới. Điều này là phá hỏng nguyên tắc hoạt động của mô hình lập trình sự kiện - phá vỡ sự đóng gói

Để giải quyết vấn đề trên, thật đơn giản với .NET chỉ cần thêm từ khóa event vào định nghĩa event_news của Pushliser, và từ đây event_news gọi là Event chứ không còn gọi là Delegate

public event NotifyNews event_news;

Từ lúc này, các Subscriber chỉ có thể đăng ký nhận sự kiện với toán tử += hoặc hủy nhận sự kiện với toán tử -= chứ không thể thực hiện gán p.event_news = null vì nếu viết code như vậy lập tức báo lỗi.

Tóm lại, Event là Delegate nhưng khi khai báo thêm từ khóa event, dẫn tới chỉ có thể thực hiện toán tử += hoặc -= với Event

Event trong thư viện .NET

Các Event ví dụ như KeyDown, GotFocus, Load của Form, Application.ApplicationExit, Application.Idle ... đều xây dựng từ một delegate là EventHandler

public delegate void EventHandler(object sender, EventArgs e);
public delegate void EventHandler(object sender, TEventArgs e)
        where TEventArgs : EventArgs;

Như vậy bạn có thể sử dụng luôn delegate EventHandler để xây dựng các Event của riêng mình sử dụng cho các Publisher, chỉ cần xây dựng các lớp phái sinh từ EventArgs với mục đích thêm vào các tham số riêng khi gửi sử kiện. Ví dụ:

public class MyEventArgs : EventArgs
{
    public MyEventArgs(string data)
    {
        this.data = data;
    }

    private string data;

    public string Data {
        get { return data; }
    }
}

public class Publisher
{
    //Tạo Event với EventHandler
    public event EventHandler<MyEventArgs> event_news;
    public void Send()
    {
        event_news?.Invoke(this, new MyEventArgs("Có tin mới"));
    }
}


public class SubscriberA
{
    public void Sub(Publisher p)
    {
            p.event_news += ReceiverFromPublisher;
    }

    private void ReceiverFromPublisher(object sender, MyEventArgs e)
    {
        Console.WriteLine("SubscriberA: " + e.Data);
    }
}
public class SubscriberB
{
    public void Sub(Publisher p)
    {
        p.event_news += ReceiverFromPublisher;
    }

    private void ReceiverFromPublisher(object sender, MyEventArgs e)
    {
        Console.WriteLine("SubscriberB: " + e.Data);
    }
}

class Program
{
    static void Main(string[] args)
    {
        Publisher p = new Publisher();
        SubscriberA sa = new SubscriberA();
        SubscriberB sb = new SubscriberB();

        sa.Sub(p);
        sb.Sub(p);

        p.Send();

    }
}

Kết quả chạy

SubscriberA: Có tin mới
SubscriberB: Có tin mới

Lưu ý: lớp MyEventArgs xây dựng kế thừa từ EventArgs ở trên với mục đích chuyên trở thêm dữ liệu, để đảm bảo dữ liệu không bị sửa đổi bởi các Subsriber thì data xây dựng theo cách thức như trên để chỉ có thể đọc! (sau khi khởi tạo từ hàm khởi tạo)

Đăng ký theo dõi ủng hộ kênh