Files
My-Notes/笔记/CSharp/进阶/事件.md
T
2026-08-09 12:41:53 +08:00

468 lines
8.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 事件
概念:
事件是==基于委托的安全封装==
它用于表示“某件事发生了”,并通知订阅这个事件的方法
简单理解:
委托像一个可以直接调用的方法列表
事件像一个“通知开关”
类外部可以订阅通知,但不能随意触发通知
作用:
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
#进阶