Files
My-Notes/笔记/CSharp/进阶/事件.md
T

468 lines
8.5 KiB
Markdown
Raw Normal View History

2026-08-09 12:41:53 +08:00
# 事件
概念:
事件是==基于委托的安全封装==
它用于表示“某件事发生了”,并通知订阅这个事件的方法
简单理解:
委托像一个可以直接调用的方法列表
事件像一个“通知开关”
类外部可以订阅通知,但不能随意触发通知
作用:
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
#进阶