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

8.5 KiB
Raw Blame History

事件

概念: 事件是==基于委托的安全封装== 它用于表示“某件事发生了”,并通知订阅这个事件的方法

简单理解: 委托像一个可以直接调用的方法列表 事件像一个“通知开关” 类外部可以订阅通知,但不能随意触发通知

作用:

  1. 当某个状态变化时通知其他对象
  2. 降低对象之间的耦合
  3. 保护委托,避免类外部随意赋值或调用
  4. 常用于按钮点击、游戏状态变化、网络消息、任务完成等场景

关键字:

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();
	}
}

输出:

任务执行中
任务完成
收到任务完成通知

理解:

  1. TaskRunner 负责执行任务
  2. 任务完成后,在类内部触发 OnFinished
  3. ShowMessage 订阅了事件
  4. 所以任务完成时,会自动执行 ShowMessage

订阅和取消订阅

订阅事件:

task.OnFinished += ShowMessage;

取消订阅:

task.OnFinished -= ShowMessage;

说明:

  1. += 表示订阅事件
  2. -= 表示取消订阅
  3. 可以有多个方法订阅同一个事件
  4. 取消订阅时,方法必须和订阅时是同一个方法

一个事件可以有多个订阅者

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();
	}
}

说明:

  1. sender 表示触发事件的对象
  2. EventArgs.Empty 表示没有额外事件数据
  3. 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();
	}
}

好处:

  1. 把“触发事件”的代码集中在 OnFinished
  2. 子类可以通过重写 OnFinished 扩展行为
  3. 事件命名和触发逻辑更清晰

常见使用场景

  1. 按钮点击后通知界面逻辑
  2. 任务完成后刷新状态
  3. 分数、血量、进度变化后更新显示
  4. 网络消息到达后通知处理模块
  5. 下载完成、计时结束、加载完成等通知

注意

  1. 事件的底层是委托
  2. 事件只能声明在类、结构体或接口中
  3. 类外部只能使用 += 订阅、-= 取消订阅
  4. 类外部不能直接赋值事件,也不能直接触发事件
  5. 事件可能没有订阅者,触发前要判空
  6. 简单事件可用 Action
  7. 标准 .NET 事件常用 EventHandlerEventHandler<TEventArgs>
  8. 不再需要监听时,应及时取消订阅

易错点

  1. 事件不是独立机制,它本质上依赖委托
  2. event 不能让外部直接调用,只能让声明类内部触发
  3. += 是订阅,不是普通赋值
  4. 事件没有订阅者时,直接调用可能出现空引用异常
  5. 订阅后忘记 -=,可能导致对象一直被引用

错误示例:

TaskRunner task = new TaskRunner();

// task.OnFinished?.Invoke();
// task.OnFinished = null;

原因: OnFinished 是事件 外部只能订阅或取消订阅,不能触发或覆盖它


一句话记忆

==事件是对委托的安全封装,用来在事情发生时通知订阅者。==


面试/复习时怎么说

可以这样答:

“事件本质上是对委托的封装,用于发布通知。
外部对象可以通过 += 订阅事件、通过 -= 取消订阅,但不能直接触发或覆盖事件。
这样可以把触发权保留在事件发布者内部,常用于按钮点击、状态变化和任务完成等场景。”


引用:

  1. Microsoft Learn: Events
  2. Microsoft Learn: EventHandler Delegate
  3. Microsoft Learn: EventHandler Delegate

#事件 #event #委托 #EventHandler #进阶