# 事件 概念: 事件是==基于委托的安全封装== 它用于表示“某件事发生了”,并通知订阅这个事件的方法 简单理解: 委托像一个可以直接调用的方法列表 事件像一个“通知开关” 类外部可以订阅通知,但不能随意触发通知 作用: 1. 当某个状态变化时通知其他对象 2. 降低对象之间的耦合 3. 保护委托,避免类外部随意赋值或调用 4. 常用于按钮点击、游戏状态变化、网络消息、任务完成等场景 关键字: ```csharp event ``` ---- # 和委托的关系 事件的底层是委托: ```csharp public event Action OnFinished; ``` 这里: `Action` 是委托类型 `OnFinished` 是事件名 可以和[[委托]]一起理解: 委托负责“存方法和调用方法” 事件负责“安全地把通知发布给外部” ---- # 委托和事件的区别 直接公开委托: ```csharp public Action OnFinished; ``` 类外部可以: ```csharp task.OnFinished = null; task.OnFinished?.Invoke(); ``` 问题: 外部可以清空委托 外部也可以擅自触发通知 使用事件: ```csharp public event Action OnFinished; ``` 类外部只能: ```csharp task.OnFinished += ShowMessage; task.OnFinished -= ShowMessage; ``` 类外部不能: ```csharp // task.OnFinished = null; // task.OnFinished?.Invoke(); ``` 说明: 事件让“触发通知”的控制权保留在声明事件的类内部 ---- # 语法 声明事件: ```csharp 访问修饰符 event 委托类型 事件名; ``` 使用自定义委托: ```csharp public delegate void FinishHandler(); public event FinishHandler OnFinished; ``` 使用 `Action`: ```csharp public event Action OnFinished; ``` 使用 `EventHandler`: ```csharp public event EventHandler OnFinished; ``` ---- # 基础案例 ```csharp using System; class TaskRunner { public event Action OnFinished; public void Run() { Console.WriteLine("任务执行中"); Console.WriteLine("任务完成"); OnFinished?.Invoke(); } } class Program { static void ShowMessage() { Console.WriteLine("收到任务完成通知"); } static void Main(string[] args) { TaskRunner task = new TaskRunner(); task.OnFinished += ShowMessage; task.Run(); } } ``` 输出: ```text 任务执行中 任务完成 收到任务完成通知 ``` 理解: 1. `TaskRunner` 负责执行任务 2. 任务完成后,在类内部触发 `OnFinished` 3. `ShowMessage` 订阅了事件 4. 所以任务完成时,会自动执行 `ShowMessage` ---- # 订阅和取消订阅 订阅事件: ```csharp task.OnFinished += ShowMessage; ``` 取消订阅: ```csharp task.OnFinished -= ShowMessage; ``` 说明: 1. `+=` 表示订阅事件 2. `-=` 表示取消订阅 3. 可以有多个方法订阅同一个事件 4. 取消订阅时,方法必须和订阅时是同一个方法 ---- # 一个事件可以有多个订阅者 ```csharp using System; class TaskRunner { public event Action OnFinished; public void Run() { Console.WriteLine("任务完成"); OnFinished?.Invoke(); } } class Program { static void ShowMessage() { Console.WriteLine("显示完成提示"); } static void SaveRecord() { Console.WriteLine("保存完成记录"); } static void Main(string[] args) { TaskRunner task = new TaskRunner(); task.OnFinished += ShowMessage; task.OnFinished += SaveRecord; task.Run(); } } ``` 输出: ```text 任务完成 显示完成提示 保存完成记录 ``` 理解: 同一个事件可以通知多个订阅者 每个订阅的方法都会被依次调用 ---- # 触发事件时要判空 不安全写法: ```csharp OnFinished(); ``` 问题: 如果没有任何方法订阅事件 `OnFinished` 就可能为 `null` 直接调用会报错 常见写法: ```csharp if (OnFinished != null) { OnFinished(); } ``` 更简洁的写法: ```csharp OnFinished?.Invoke(); ``` 说明: `?.Invoke()` 表示事件不为 `null` 时才调用 ---- # 带参数的事件 事件可以使用带参数的 `Action`: ```csharp using System; class ScoreSystem { public event Action OnScoreChanged; public void AddScore(int score) { OnScoreChanged?.Invoke(score); } } class Program { static void ShowScore(int score) { Console.WriteLine("获得分数:" + score); } static void Main(string[] args) { ScoreSystem system = new ScoreSystem(); system.OnScoreChanged += ShowScore; system.AddScore(100); } } ``` 输出: ```text 获得分数:100 ``` 说明: `Action` 表示订阅方法需要接收一个 `int` 参数 ---- # EventHandler 标准写法 在 .NET 中,事件常使用 `EventHandler`: ```csharp using System; class TaskRunner { public event EventHandler Finished; public void Run() { Console.WriteLine("任务完成"); Finished?.Invoke(this, EventArgs.Empty); } } class Program { static void OnTaskFinished(object sender, EventArgs e) { Console.WriteLine("收到任务完成通知"); } static void Main(string[] args) { TaskRunner task = new TaskRunner(); task.Finished += OnTaskFinished; task.Run(); } } ``` 说明: 1. `sender` 表示触发事件的对象 2. `EventArgs.Empty` 表示没有额外事件数据 3. `EventHandler` 是 .NET 中常见的事件委托类型 ---- # EventHandler 如果事件需要传更多信息,可以自定义事件参数类: ```csharp using System; class ScoreChangedEventArgs : EventArgs { public int Score { get; } public ScoreChangedEventArgs(int score) { Score = score; } } class ScoreSystem { public event EventHandler ScoreChanged; public void AddScore(int score) { ScoreChanged?.Invoke(this, new ScoreChangedEventArgs(score)); } } class Program { static void OnScoreChanged(object sender, ScoreChangedEventArgs e) { Console.WriteLine("获得分数:" + e.Score); } static void Main(string[] args) { ScoreSystem system = new ScoreSystem(); system.ScoreChanged += OnScoreChanged; system.AddScore(100); } } ``` 输出: ```text 获得分数:100 ``` 理解: `ScoreChangedEventArgs` 专门用来保存事件附带的数据 订阅方法通过 `e.Score` 获取具体信息 ---- # 推荐的触发方式 事件一般由声明它的类负责触发: ```csharp class TaskRunner { public event EventHandler Finished; protected virtual void OnFinished() { Finished?.Invoke(this, EventArgs.Empty); } public void Run() { Console.WriteLine("任务完成"); OnFinished(); } } ``` 好处: 1. 把“触发事件”的代码集中在 `OnFinished` 2. 子类可以通过重写 `OnFinished` 扩展行为 3. 事件命名和触发逻辑更清晰 ---- # 常见使用场景 1. 按钮点击后通知界面逻辑 2. 任务完成后刷新状态 3. 分数、血量、进度变化后更新显示 4. 网络消息到达后通知处理模块 5. 下载完成、计时结束、加载完成等通知 ---- # 注意 1. 事件的底层是委托 2. 事件只能声明在类、结构体或接口中 3. 类外部只能使用 `+=` 订阅、`-=` 取消订阅 4. 类外部不能直接赋值事件,也不能直接触发事件 5. 事件可能没有订阅者,触发前要判空 6. 简单事件可用 `Action` 7. 标准 .NET 事件常用 `EventHandler` 或 `EventHandler` 8. 不再需要监听时,应及时取消订阅 ---- # 易错点 1. 事件不是独立机制,它本质上依赖委托 2. `event` 不能让外部直接调用,只能让声明类内部触发 3. `+=` 是订阅,不是普通赋值 4. 事件没有订阅者时,直接调用可能出现空引用异常 5. 订阅后忘记 `-=`,可能导致对象一直被引用 错误示例: ```csharp TaskRunner task = new TaskRunner(); // task.OnFinished?.Invoke(); // task.OnFinished = null; ``` 原因: `OnFinished` 是事件 外部只能订阅或取消订阅,不能触发或覆盖它 ---- # 一句话记忆 ==事件是对委托的安全封装,用来在事情发生时通知订阅者。== ---- # 面试/复习时怎么说 可以这样答: “事件本质上是对委托的封装,用于发布通知。 外部对象可以通过 `+=` 订阅事件、通过 `-=` 取消订阅,但不能直接触发或覆盖事件。 这样可以把触发权保留在事件发布者内部,常用于按钮点击、状态变化和任务完成等场景。” ---- 引用: 1. Microsoft Learn: [Events](https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/events/) 2. Microsoft Learn: [EventHandler Delegate](https://learn.microsoft.com/en-us/dotnet/api/system.eventhandler) 3. Microsoft Learn: [EventHandler Delegate](https://learn.microsoft.com/en-us/dotnet/api/system.eventhandler-1) #事件 #event #委托 #EventHandler #进阶