C#事件(Event)机制详解(附带实例)
简单来说,事件就是一件事情发生,然后有一个响应通知(notify)外界此事情发生了。
我们设计程序时会将不同的状况设计成一个事件,然后会针对此事件设计处理程序(Handler),同时将事件与处理程序绑在一起,这样事件发生时,会触发我们设计的处理程序去处理这个事件。
上图是一个简单的概念,在真实的程序设计环境中,引发事件的称为发布者,收到事件通知的称订阅者,一个事件可能有多个订阅者,有兴趣处理事件的订阅者,需要注册事件,然后定义当事件发生时要处理的程序,也就是设计处理程序可以响应事件。
C# 将事件封装在委托中,此委托定义方法签名,也就是方法的参数类型和回传数据类型,所以设计事件处理程序时,需遵照方法签名的要求。
下列是一个实例:
这个部分设计的重点如下:
【实例 1】第1个事件程序,从这个程序读者可以认识声明事件、注册事件管理程序、触发事件过程,以及执行事件管理程序。
上述程序基本上分成 3 块,上半部是订阅者,中间是发布者在声明委托 delegate,最下方是发布者。这个程序的执行过程如下:
【实例 2】使用 C# 内置的 EventHandler 委托,替换委托 delegate,重新设计实例 1,这个实例仍是没有传递任何数据。
上述程序使用了 C# 内置的 EventHandler 替换了自行声明委托 delegate,让程序变得更单纯,因为上述程序假设没有传递任何事件数据,所以执行第 18 行调用 EventAHappened() 方法时参数是 EventArgs.Empty,当执行第 20 行时参数 EventArgs e 的 e 是空的,第 22 行触发 EventA 事件处理程序时,参数 this 是扮演传递者的角色。然后执行第 2 行 EventAHandler(),第 1 个参数就是 this,第 2 个参数是空值 e,最后程序输出“EventA事件处理完成”。
【实例 3】重新设计实例 2,传递 true 信息给事件处理程序,然后事件处理程序输出成功字样。
【实例 4】传递 true 与 DateTime.Now 时间数据,时间是指当下 EventA 的完成时间数据。
我们设计程序时会将不同的状况设计成一个事件,然后会针对此事件设计处理程序(Handler),同时将事件与处理程序绑在一起,这样事件发生时,会触发我们设计的处理程序去处理这个事件。

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

C# 将事件封装在委托中,此委托定义方法签名,也就是方法的参数类型和回传数据类型,所以设计事件处理程序时,需遵照方法签名的要求。
C#声明事件
声明一个事件可以分成两个步骤:- 声明一个委托 delegate;
- 使用关键词 event 和步骤 1 的委托,声明事件名称。
下列是一个实例:
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,最下方是发布者。这个程序的执行过程如下:
- 第 12 行:声明委托 Notify();
- 第 7 行:实例化 Publisher 物件,委托 Notify 与事件 EventA 声明完成;
- 第 8 行:注册 EventA 处理程序 EventHandler;
- 第 9 行:开始 obj.StartEventA();
- 第 17 行:StartEventA();
- 第 19 行:输出 EventA 事件触发;
- 第 21 行:调用 EventAHappened();
- 第 23 行:开始 EventAHappened();
- 第 25 行:触发 EventA 事件处理程序;
- 第 2 行:开始 EventAHandler();
- 第 4 行:输出 EventA 事件处理完成。
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 事件处理 成功
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日