委托是类型,事件是基于委托的封装机制;正确做法是用event关键字声明事件并配合?.invoke()安全触发,避免public委托字段、async void和内存泄漏。
☞☞☞AI 智能聊天, 问答助手, AI 智能搜索, 免费无限量使用 DeepSeek R1 模型☜☜☜

委托和事件不是一回事,别用 EventHandler 硬套所有场景
委托是类型,事件是基于委托的封装机制;直接把 Action 或自定义委托当事件字段暴露,等于放弃访问控制。C# 的 event 关键字本质是编译器帮你加了“只能 += / -=,不能直接赋值”的限制。
- 常见错误:在类里写
public Action<string> OnDataReceived;</string>,然后外部直接obj.OnDataReceived = x => {...}—— 这不是事件,是公开委托字段,多处订阅会相互覆盖 - 正确做法:用
event声明,搭配protected virtual void OnDataReceived(string data)模板方法供子类重写 - 性能影响:
event底层仍是多播委托,但每次 += 都要新建委托实例;高频触发时(如帧循环),建议用WeakEventManager或手动管理订阅生命周期
泛型委托 Func 和 Action 足够用,别急着写自定义委托
.NET 6+ 已内置 Action<t1></t1>(最多16参数)和 Func<t1></t1>(最多16入参+1返回值),95% 的回调场景无需定义新委托类型。
- 使用场景:UI按钮点击传参、异步完成回调、LINQ 中的
Select或Where条件函数 - 参数差异:
Action无返回值,Func必须有返回值;误用会导致编译错误,比如把Func<bool></bool>当成Action传给Task.Run - 兼容性注意:.NET Framework 4.0+ 支持到
Action<t1></t1>,再往上需确认目标框架版本;若需跨老版本,宁可写一个简单委托,也别依赖高阶泛型
+= 订阅事件时,记得检查 null 再触发
事件字段默认为 null,直接调用 MyEvent?.Invoke(...) 是安全的,但很多人在模板方法里漏掉判空,或在多线程下出现竞态:订阅刚被移除,触发前就已为 null。
- 常见错误现象:
NullReferenceException在MyEvent.Invoke(...)行抛出,尤其在 WPF/WinForms 的 UI 线程与后台线程混用时 - 实操建议:统一用
?.Invoke(),不要手写if (MyEvent != null) MyEvent(...)—— 后者在多线程下仍有风险 - 进阶处理:对关键事件(如资源释放通知),可用
Interlocked.CompareExchange+ 委托快照,但多数业务代码没必要,?.Invoke()足够
异步事件处理别用 async void,改用 async Task + 手动 await
async void 是唯一无法被等待的异步签名,一旦在事件处理器里用了它,异常会直接崩掉线程,且上层完全无法捕获或协调。
- 错误示范:
myButton.Click += async void (_, _) => { await LoadData(); }—— 异常吞掉,调试极难定位 - 正确做法:声明普通事件处理器,内部
await并显式处理异常,或用Task.Run包一层(仅限 CPU 密集型) - 性能提醒:频繁触发的事件(如鼠标移动)里做
await,容易堆积未完成 Task;此时应节流(debounce)或改用同步轻量逻辑
委托和事件的核心约束其实就两条:访问控制靠 event 关键字,执行安全靠 ?.Invoke() 和避免 async void。真正容易被忽略的是——事件订阅者生命周期比发布者长时,不主动 -= 就会造成内存泄漏,尤其在 MVVM 或插件系统里。










