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日
ICP备案:
公安联网备案: