468 lines
8.5 KiB
Markdown
468 lines
8.5 KiB
Markdown
|
|
# 事件
|
|||
|
|
概念:
|
|||
|
|
事件是==基于委托的安全封装==
|
|||
|
|
它用于表示“某件事发生了”,并通知订阅这个事件的方法
|
|||
|
|
|
|||
|
|
简单理解:
|
|||
|
|
委托像一个可以直接调用的方法列表
|
|||
|
|
事件像一个“通知开关”
|
|||
|
|
类外部可以订阅通知,但不能随意触发通知
|
|||
|
|
|
|||
|
|
作用:
|
|||
|
|
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<int> 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>` 表示订阅方法需要接收一个 `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<TEventArgs>
|
|||
|
|
如果事件需要传更多信息,可以自定义事件参数类:
|
|||
|
|
|
|||
|
|
```csharp
|
|||
|
|
using System;
|
|||
|
|
|
|||
|
|
class ScoreChangedEventArgs : EventArgs
|
|||
|
|
{
|
|||
|
|
public int Score { get; }
|
|||
|
|
|
|||
|
|
public ScoreChangedEventArgs(int score)
|
|||
|
|
{
|
|||
|
|
Score = score;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
class ScoreSystem
|
|||
|
|
{
|
|||
|
|
public event EventHandler<ScoreChangedEventArgs> 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<TEventArgs>`
|
|||
|
|
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<TEventArgs> Delegate](https://learn.microsoft.com/en-us/dotnet/api/system.eventhandler-1)
|
|||
|
|
|
|||
|
|
#事件
|
|||
|
|
#event
|
|||
|
|
#委托
|
|||
|
|
#EventHandler
|
|||
|
|
#进阶
|