C# 学习笔记 18:解耦神器!高级通信魔法——委托与事件 (Delegates & Events)
开篇:皮皮的“全能”魔王
皮皮(Pipi)正在写关底 Boss 的死亡逻辑。
当魔王死亡时,游戏需要做三件事:
他写出了这样一段代码:
publicclassBoss{publicvoidDie() { Console.WriteLine("魔王倒下了!");// 🚫 高耦合写法 SoundManager.PlayVictory(); UIManager.ShowWinScreen(); AchievementManager.Unlock("DragonSlayer"); }}
皮皮很满意:
“这不是很完美吗?”
瓜瓜摇头:
“大错特错。”
如果哪天:
Boss 类会直接报错。
问题的本质是:
❗ 魔王只管“死”,❗ 凭什么操心 UI、音效、成就?
这叫——强耦合。
我们需要让 Boss:
📢 “发个朋友圈”谁关心它的死亡,谁自己订阅。
这,就是——委托与事件。
第一关:把方法装进变量里 —— 委托(Delegate)
以前我们学过:
int x = 5;
变量存的是“数据”。
而 委托(Delegate) 是一种特殊变量:
它存的是 —— 方法(代码)。
第一步:制定契约
// 规定:必须是“无参数、无返回值”的方法publicdelegatevoidDeathAction();
它像一份合同:
“只有符合签名的方法才能进来。”
第二步:在 Boss 里声明委托变量
publicclassBoss{public DeathAction OnDeath;publicvoidDie() { Console.WriteLine("魔王倒下了!");if (OnDeath != null) { OnDeath(); } }}
现在:
Boss 只负责在死亡时执行:
OnDeath();
它完全不知道谁会响应。
第二关:神奇的加号 —— 多播委托
现在,各个系统可以自己订阅 Boss 的死亡。
Boss myBoss = new Boss();// 音效系统订阅myBoss.OnDeath += SoundManager.PlayVictory;// UI 系统订阅myBoss.OnDeath += UIManager.ShowWinScreen;// 魔王死亡myBoss.Die();
执行顺序是:
PlayVictory()ShowWinScreen()
一个委托可以同时装多个方法。
这叫:
多播委托(Multicast Delegate)
现在 Boss 类变成:
“我死了。”至于谁听到,谁行动——我不管。
这叫:
✅ 解耦(Decoupling)

第三关:真正的安全版本 —— 事件(Event)
上面的写法还有个巨大隐患。
因为:
public DeathAction OnDeath;
是 public。
外部可以干坏事:
// 手滑用 = 清空所有订阅myBoss.OnDeath = PipiManager.LootGold;// 外部直接触发myBoss.OnDeath();
这会造成:
解决方案:event 关键字
publicclassBoss{publicevent DeathAction OnDeath;publicvoidDie() { OnDeath?.Invoke(); }}
event 相当于给委托加了一层防弹玻璃。
event 的两大保护机制
1️⃣ 外部不能用 = 赋值(只能 += 和 -=)
2️⃣ 外部不能直接调用(只有类内部才能 Invoke)
总结一句话:
Delegate 是能力 Event 是安全封装

第四关:现代 C# 的懒人写法 —— Action / Func
每次写:
publicdelegatevoidDeathAction();
太麻烦。
.NET 已经帮我们准备好了通用版本。
Action —— 无返回值
publicevent Action OnDeath;
Action—— 带参数
publicevent Action<int> OnDeath;
Func—— 有返回值
Func<int, int, int> add = (a, b) => a + b;
现代写法通常直接用:
publicevent Action OnDeath;
简洁、优雅、工业级。
核心架构思想
委托与事件的本质是:
📢 发布者(Boss)📩 订阅者(UI / 音效 / 成就)
发布者只负责:
发生了什么
订阅者自己决定:
我要做什么
这就是:
解耦架构
今日总结
课后思考
在 Unity / WinForm / WPF 中,我们经常会写:
button.Click += MyMethod;
现在你应该能理解:
UI 框架的底层通信核心:
几乎全部基于事件模型。
下期预告
皮皮准备做联机功能。
当程序向服务器请求玩家数据时:
整个游戏突然卡住。
原因是:
主线程在等待网络返回。
下一期我们学习:
异步编程(Async / Await)
让程序学会:
我们将正式进入:
C# 现代编程核心领域。