事件驱动是GUI编程的核心逻辑。当程序被按钮点击、按键或定时器中断时,如何规范处理事件?.NET框架通过EventHandler委托给出了标准答案。
🔍 一、EventHandler委托:事件处理的基石
public delegate void EventHandler(object sender, EventArgs e);
参数解析:
- object sender → 事件源对象(任意类型)
- EventArgs e → 事件数据容器(默认为空)
核心特性: - 强制统一签名:所有事件处理器必须匹配此参数结构
- 返回值必须为void
- EventArgs是空壳基类,实际数据需通过派生类扩展
💡 设计意义:通过标准化参数,实现事件处理接口的统一化,避免签名混乱。
⚙️ 二、基础事件实现(无数据传递)
// 发布者
class Incrementer {public event EventHandler CountedADozen; // 声明标准事件 public void DoCount() {for(int i=1; i<100; i++)if(i%12 == 0 && CountedADozen != null)CountedADozen(this, null); // 触发事件(无数据)}
}// 订阅者
class Dozens {void IncrementDozensCount(object source, EventArgs e) {DozensCount++; // 忽略参数e }
}
适用场景:仅需事件通知,无需传递额外数据(如界面按钮点击)
🚀 三、扩展事件数据:自定义EventArgs
步骤详解:
1️⃣ 创建派生类存储数据
public class IncrementerEventArgs : EventArgs {public int IterationCount { get; set; } // 自定义数据属性
}
2️⃣ 使用泛型委托声明事件
class Incrementer {// 泛型委托绑定自定义参数类型 public event EventHandler<IncrementerEventArgs> CountedADozen;
}
3️⃣ 触发事件时传递数据
public void DoCount() {var args = new IncrementerEventArgs(); // 创建数据对象 for(int i=1; i<100; i++) {if(i%12 == 0 && CountedADozen != null) {args.IterationCount = i; // 设置数据 CountedADozen(this, args); // 传递数据对象 }}
}
4️⃣ 订阅者接收数据
void IncrementDozensCount(object source, IncrementerEventArgs e) {Console.WriteLine($"触发位置:{e.IterationCount}"); // 使用自定义数据
}
⚠️ 命名规范:自定义类名需以EventArgs结尾
🔄 四、事件处理程序管理
动态增删处理器:
// 添加处理器
p.SimpleEvent += s.MethodA;
p.SimpleEvent += s.MethodB;// 移除指定处理器
p.SimpleEvent -= s.MethodB;
关键规则:
- 同个处理器多次注册时,-=只移除最后一次注册
- 事件触发前必须检查null(避免空引用异常)
- 线程安全场景需使用Invoke同步调用
场景 | 方案 | 示例 |
---|---|---|
无数据传递 | 原生EventHandler | 按钮点击事件 |
需传递数据 | EventHandler+自定义EventArgs | 进度条数值更新 |
多订阅者管理 | +=/-=动态注册 | 动态加载模块 |
// 终极模板
public event EventHandler<CustomEventArgs> MyEvent;
public class CustomEventArgs : EventArgs { ... }
🚀 行动建议:
- 立即重构旧事件代码,采用标准模式
- 复杂数据场景务必使用自定义EventArgs
- 关键事件添加try-catch异常处理