====== 事件 (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` 关键字,就是为了保证**“发布者负责触发,订阅者负责处理,除此之外谁也不能乱动”**的契约。