首页 > 编程笔记 > C#笔记 阅读:22

C#事件(Event)机制详解(附带实例)

简单来说,事件就是一件事情发生,然后有一个响应通知(notify)外界此事情发生了。

我们设计程序时会将不同的状况设计成一个事件,然后会针对此事件设计处理程序(Handler),同时将事件与处理程序绑在一起,这样事件发生时,会触发我们设计的处理程序去处理这个事件。


上图是一个简单的概念,在真实的程序设计环境中,引发事件的称为发布者,收到事件通知的称订阅者,一个事件可能有多个订阅者,有兴趣处理事件的订阅者,需要注册事件,然后定义当事件发生时要处理的程序,也就是设计处理程序可以响应事件。


C# 将事件封装在委托中,此委托定义方法签名,也就是方法的参数类型和回传数据类型,所以设计事件处理程序时,需遵照方法签名的要求。

C#声明事件

声明一个事件可以分成两个步骤:
下列是一个实例:
public delegate void Notify();            // 声明委托

public class Publisher                   // 发布者 Publisher 类
{
    public event Notify EventA;          // 声明事件名称 EventA
    ...
}

C#设计事件触发位置

事件触发位置通常是在 Publisher 类内设定的方法。在笔者的第 1 个实例中采用下列方式设计:
public class Publisher
{
    public event Notify EventA;

    public void EventAHappened()
    {
        // 若没有注册事件,可以避免触发 null 错误
        EventA?.Invoke();              // 触发事件管理程序
    }
}

C#注册事件

在过去的 C# 语法中,Main() 方法是 C# 程序入口点,这是在一个类内,这个类就是订阅者 Subscriber 类。顶级语句的设计则省略了订阅者 Subscribe r类,所以顶级语句就扮演订阅者的角色。

这个部分设计的重点如下:
static void EventAHandler()             // EventA 事件处理程序
{
    ...
}

Publisher obj = new Publisher();        // 实体化 Publisher 对象
obj.EventA += EventAHandler;           // 注册事件处理程序

【实例 1】第1个事件程序,从这个程序读者可以认识声明事件、注册事件管理程序、触发事件过程,以及执行事件管理程序。
// --- Subscriber 订阅者 ---
static void EventAHandler()           // EventA 事件处理程序
{
    Console.WriteLine("EventA 事件处理完成");
}

Publisher obj = new Publisher();      // 实体化 Publisher 对象 obj
obj.EventA += EventAHandler;         // 注册事件处理程序
obj.StartEventA();                   // 启动 EventA

// --- 以下是 Publisher 发布者 ---
public delegate void Notify();       // Publisher 声明委托 delegate

public class Publisher             // 扮演 Publisher
{
    public event Notify EventA;    // 声明事件 EventA
    public void StartEventA()
    {
        Console.WriteLine("EventA 事件触发");
        // 执行工作...
        EventAHappened();          // EventA 发生了
    }
    protected virtual void EventAHappened()
    {
        EventA?.Invoke();         // 触发 EventA 事件处理程序
    }
}
执行结果为:

EventA 事件触发
EventA 事件处理完成


上述程序基本上分成 3 块,上半部是订阅者,中间是发布者在声明委托 delegate,最下方是发布者。这个程序的执行过程如下:

C#内置的事件处理程序委托

C# 内置了适合大多数事件的 EventHandler 和 EventHandler<TEventArgs> 委托,通常任何事件均需要传递两个参数,分别是事件来源和事件数据,EventHandler 可以对不包含事件数据的所有事件执行委托。如果需要传递数据给事件处理程序,则使用 EventHandler<TEventArgs> 执行委托。

【实例 2】使用 C# 内置的 EventHandler 委托,替换委托 delegate,重新设计实例 1,这个实例仍是没有传递任何数据。
// --- Subscriber ---
static void EventAHandler(object sender, EventArgs e) // EventA 事件处理程序
{
    Console.WriteLine("EventA 事件处理完成");
}

Publisher obj = new Publisher();      // 实体化 Publisher 对象 obj
obj.EventA += EventAHandler;         // 注册事件处理程序
obj.StartEventA();                   // 启动 EventA

// --- 以下是 Publisher ---
public class Publisher
{
    public event EventHandler EventA; // 声明事件 EventA
    public void StartEventA()
    {
        Console.WriteLine("EventA 事件触发");
        EventAHappened(EventArgs.Empty); // EventA 发生了,没传数据
    }
    protected virtual void EventAHappened(EventArgs e)
    {
        EventA?.Invoke(this, e);         // 触发 EventA 事件处理程序
    }
}
执行结果与实例 1 相同。

上述程序使用了 C# 内置的 EventHandler 替换了自行声明委托 delegate,让程序变得更单纯,因为上述程序假设没有传递任何事件数据,所以执行第 18 行调用 EventAHappened() 方法时参数是 EventArgs.Empty,当执行第 20 行时参数 EventArgs e 的 e 是空的,第 22 行触发 EventA 事件处理程序时,参数 this 是扮演传递者的角色。然后执行第 2 行 EventAHandler(),第 1 个参数就是 this,第 2 个参数是空值 e,最后程序输出“EventA事件处理完成”。

C#传递事件数据

大多数的事件会向订阅者传送数据,EventArgs 是所有事件数据的基类,.NET 也包含许多内置事件的类,此外,你也可以通过对 EventArgs 类进行派生来自行定义数据类。

【实例 3】重新设计实例 2,传递 true 信息给事件处理程序,然后事件处理程序输出成功字样。
// --- Subscriber ---
static void EventAHandler(object sender, bool IsSuccess)  // 带数据参数
{
    Console.WriteLine("EventA 事件处理 " + (IsSuccess ? "成功" : "失败"));
}

Publisher obj = new Publisher();
obj.EventA += EventAHandler;
obj.StartEventA();

// --- 以下是 Publisher ---
public class Publisher
{
    public event EventHandler<bool> EventA;
    public void StartEventA()
    {
        Console.WriteLine("EventA 事件触发");
        EventAHappened(true);            // 传递 true 数据
    }
    protected virtual void EventAHappened(bool IsSuccess)
    {
        EventA?.Invoke(this, IsSuccess);
    }
}
执行结果为:

EventA 事件触发
EventA 事件处理 成功

上述程序第 18 行笔者传送 true 给 EventAHappened(),通过触发 EventA 事件处理程序,第 2 行的参数 IsSuccess 是 true,所以最后可以得到“成功”字样。

C#传送自定义时间数据

接下来笔者要传送多个数据给事件处理程序,这时就需要定义类,同时这个类需要继承 EventArgs 基类,如下所示:
class DataEventArgs : EventArgs
{
    public bool IsSuccess { get; set; }
    public DateTime HappenTime { get; set; }
}
下列实例主要讲解如何传递上述类数据。

【实例 4】传递 true 与 DateTime.Now 时间数据,时间是指当下 EventA 的完成时间数据。
// --- Subscriber ---
static void EventAHandler(object sender, DataEventArgs e)  // 事件处理程序
{
    Console.WriteLine("EventA 事件处理 " + (e.IsSuccess ? "成功" : "失败"));
    Console.WriteLine("EventA 完成时间 " + e.HappenTime.ToLongDateString());
}

Publisher obj = new Publisher();
obj.EventA += EventAHandler;
obj.StartEventA();

// --- 以下是 Publisher ---
public class DataEventArgs : EventArgs     // 定义要传送的数据类
{
    public bool IsSuccess { get; set; }     // true 或 false
    public DateTime HappenTime { get; set; } // 时间数据
}

public class Publisher
{
    public event EventHandler<DataEventArgs> EventA;
    public void StartEventA()
    {
        var msg = new DataEventArgs();
        Console.WriteLine("EventA 事件触发");
        msg.IsSuccess = true;            // 传送 true
        msg.HappenTime = DateTime.Now;  // 传送当前时间
        EventAHappened(msg);
    }
    protected virtual void EventAHappened(DataEventArgs e)
    {
        EventA?.Invoke(this, e);
    }
}
执行结果为:

EventA 事件触发
EventA 事件处理 成功
EventA 完成时间 2023年8月11日

上述是传送类数据的实例,读者可以从程序的箭头了解数据传送方式。

相关文章