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