====== 事件 (Events) ====== 事件 (Event) 是 C# 中非常核心但也相对难以理解的概念。它是类与类之间通信的重要机制。 ===== 事件简介 ===== **什么是事件?** 指某个类的对象在运行过程中,遇到了一些**特定的事情**。 * 这些特定的事情需要**通知**到对象的使用者(订阅者)。 * 当发生与某个事件相关的动作时,类就会使用事件将这个对象通知给用户。 * 这种通知的操作称为 **引发事件 (Raising an Event)**。 **为什么说事件很难理解?** 很多初学者容易混淆“委托”和“事件”。 简单来说:**事件是基于委托的**。如果把委托看作是“函数指针的容器”,那么事件就是对这个容器的一种“封装”和“保护”。 我们可以用生活中的例子来类比: * **发布者 (Publisher)**:烧水壶。 * **订阅者 (Subscriber)**:你。 * **事件**:水开了(特定的事情)。 * **处理方法**:当水开了,水壶(发布者)发出鸣叫(通知),你(订阅者)听到后去关火(处理方法)。 ===== 委托的发布与订阅 ===== 在学习 `event` 关键字之前,我们先看看如何仅使用 **委托 (Delegate)** 来实现发布和订阅模式。这有助于理解事件的底层原理。 根据教程定义,通过委托实现事件处理的过程,通常需要以下 4 个步骤: - **(1) 定义委托类型,并在发布者类中定义一个该类型的公有成员。** - **(2) 在订阅者类中定义委托处理方法。** - **(3) 订阅者对象将其事件处理方法链接到发布者对象的委托成员(一个委托类型的引用)上。** - **(4) 发布者对象在特定的情况下“激发”委托操作,从而自动调用订阅者对象的委托处理方法。** ==== 代码示例:使用纯委托 ==== using System; namespace DelegateDemo { // 1. 定义委托类型 public delegate void NotifyEventHandler(); // 发布者类 (Publisher) public class Heater { // 1. 在发布者类中定义一个该类型的公有成员 (注意:这里是 public 字段) public NotifyEventHandler OnBoil; public void BoilWater() { Console.WriteLine("水壶:开始烧水..."); // 模拟烧水过程 System.Threading.Thread.Sleep(2000); // 4. 发布者在特定情况下“激发”委托 if (OnBoil != null) { Console.WriteLine("水壶:水开了!(激发委托)"); OnBoil(); // 调用委托列表中的所有方法 } } } // 订阅者类 (Subscriber) public class Alarm { // 2. 在订阅者类中定义委托处理方法 public void MakeAlert() { Console.WriteLine("报警器:滴滴滴!水开了!"); } } class Program { static void Main(string[] args) { Heater heater = new Heater(); Alarm alarm = new Alarm(); // 3. 订阅者将处理方法链接到发布者的委托成员上 heater.OnBoil += alarm.MakeAlert; // 开始运行 heater.BoilWater(); } } } **纯委托模式的缺陷:** 在上面的代码中,`OnBoil` 是一个 `public` 字段。这意味着外部类不仅可以订阅 (`+=`),还可以: * 直接赋值 (`heater.OnBoil = null;`) —— 这会清空所有其他人的订阅! * 直接调用 (`heater.OnBoil();`) —— 即使水没开,外部也能伪造事件! 这破坏了封装性,因此我们需要 **事件 (Event)**。 ===== 事件的发布与订阅 ===== 为了解决纯委托暴露出的安全问题,C# 引入了 `event` 关键字。 **事件**本质上是对**委托**的封装。它限制了外部类对委托的操作权限。 **事件的特性:** * 在定义事件的类内部:可以像普通委托一样调用(引发事件)。 * 在定义事件的类外部:**只能**使用 `+=` (订阅) 和 `-=` (取消订阅),**不能**使用 `=` (赋值) 或直接调用。 ==== 代码示例:使用 event 关键字 ==== 我们将代码稍作修改,只需加上 `event` 关键字。 using System; namespace EventDemo { // 定义委托类型 public delegate void NotifyEventHandler(); public class Heater { // 【关键修改】这里加上了 event 关键字 // 虽然它还是 public,但编译器会对它进行保护 public event NotifyEventHandler OnBoil; public void BoilWater() { Console.WriteLine("水壶:开始烧水..."); // 只有在类的内容,才能调用这个事件 if (OnBoil != null) { OnBoil(); } } } public class Alarm { public void MakeAlert() { Console.WriteLine("报警器:滴滴滴!"); } } class Program { static void Main(string[] args) { Heater heater = new Heater(); Alarm alarm = new Alarm(); // 订阅事件 (合法) heater.OnBoil += alarm.MakeAlert; // heater.OnBoil = null; // 【报错】无法在外部直接赋值 // heater.OnBoil(); // 【报错】无法在外部直接调用 heater.BoilWater(); } } } ==== 总结 ==== | 特性 | 公有委托字段 (Delegate) | 事件 (Event) | | **定义方式** | `public DelegateType Name;` | `public event DelegateType Name;` | | **外部订阅** | 支持 `+=` 和 `-=` | 支持 `+=` 和 `-=` | | **外部赋值** | 支持 `=` (危险,会覆盖他人) | **禁止** (编译错误) | | **外部调用** | 支持 `Name()` (危险,可伪造) | **禁止** (编译错误) | | **安全性** | 低 | 高 (封装性好) | 使用 `event` 关键字,就是为了保证**“发布者负责触发,订阅者负责处理,除此之外谁也不能乱动”**的契约。