C#事件是带访问控制的委托,核心价值在于防止外部随意调用或清空处理逻辑;声明时优先用EventHandler<T>,触发须封装OnXXX方法并用?.安全调用,订阅后须显式取消以避免循环引用。

直接说结论:C# 事件不是“用”出来的,而是“设计出来再安全暴露”的——它本质是带访问控制的委托,核心价值在于防止外部随意调用或清空处理逻辑。别一上来就写 event Action MyEvent 然后 MyEvent?.Invoke(),这样容易漏掉 null 检查、线程安全和内存泄漏。
声明事件时该用 EventHandler 还是自定义委托?
优先用 EventHandler 或 EventHandler<teventargs></teventargs>,除非你明确需要非标准签名(比如不带 object sender)。系统提供的泛型版本能自动约束参数类型,避免手写委托出错。
-
EventHandler:适合无参或只用EventArgs.Empty的场景,如public event EventHandler Loaded; -
EventHandler<myeventargs></myeventargs>:需要传业务数据时用,MyEventArgs必须继承EventArgs,否则无法被 WPF/WinForms/Blazor 等框架识别 - 别自己写
public delegate void DataReadyHandler(string data);—— 除非你在做底层通信协议适配,否则纯属增加维护成本
触发事件必须写 OnXXX 方法吗?
不是必须,但强烈建议。直接调用 MyEvent?.Invoke(...) 在类外也能写,但会破坏封装性,且无法被派生类覆盖或扩展。
-
protected virtual void OnLoaded(EventArgs e)是标准模式,既支持子类重写,又把 null 检查收口在一处 - 如果事件名是
Loaded,对应方法名就叫OnLoaded,这是 .NET 命名惯例,别写成TriggerLoaded或FireLoaded - 调用前不用手动判空:用
MyEvent?.Invoke(this, e)就够了,?.已处理 null
订阅事件后为什么对象不释放?
这是最常踩的坑:订阅者(比如窗体、ViewModel)持有发布者(比如服务类)的引用,而发布者又通过事件委托反向持有订阅者的引用,形成循环引用,GC 清不掉。
- 显式取消订阅:
publisher.MyEvent -= handler;,尤其在IDisposable.Dispose()或窗体OnClosed里做 - 用弱事件模式(WeakEventManager)仅限 WPF 场景;Blazor 中改用组件生命周期钩子(
DisposeAsync) - Lambda 订阅(
publisher.MyEvent += (s, e) => { ... };)无法取消,除非把 lambda 提前赋给变量再传入
Blazor 里 @onclick 和 C# 事件是一回事吗?
不是。Blazor 的 @onclick 是 DOM 事件绑定语法,背后由框架生成 JS 互操作调用,它触发的是组件内的 C# 方法,不是传统 event 成员。你不能在 Blazor 组件里写 public event EventHandler Clicked; 然后指望 HTML 按钮自动触发它。
- Blazor 中真正用到 C# 事件的地方,是组件间通信(比如父组件订阅子组件的
OnSubmit事件)或服务通知(如AuthenticationStateProvider.AuthenticationStateChanged) -
@onclick="HandleClick"的HandleClick方法签名可以是void HandleClick()或void HandleClick(MouseEventArgs e),框架自动注入参数,你不用管委托类型 - 如果真要跨组件发通知,推荐用
CascadingParameter+EventCallback,比裸事件更符合 Blazor 设计哲学
事件机制本身不难,难的是在不同上下文里选对抽象层级:WinForms 里靠事件驱动 UI,后台服务里靠事件解耦模块,Blazor 里则要分清 DOM 绑定和 C# 通知的边界。别为了“用事件”而用,先想清楚谁该知道什么、什么时候知道、知道了之后能不能被 GC 掉。



















