不能直接用 eventargs.empty 传数据,因为它是只读静态实例,无法携带业务数据;必须派生继承 eventargs 的自定义类,含 public get-only 属性和构造注入。

为什么不能直接用 EventArgs.Empty 传数据
因为 EventArgs.Empty 是只读静态实例,没有字段可写,更无法携带业务数据。一旦需要在事件触发时告诉订阅方“哪个用户登录了”“文件下载进度到多少”,就必须派生自己的事件参数类——这不是可选项,是硬性前提。
常见错误现象:NullReferenceException 或订阅方法收不到关键字段,往往是因为忘了在自定义类里声明 public 属性,或属性没加 get 访问器。
- 必须继承
EventArgs(不是实现接口,是 class 继承) - 所有要传递的数据字段,必须声明为
public且带get(只读即可,不建议暴露 set) - 构造函数应接收全部必要参数,并赋值给对应属性
- 避免在参数类中放大对象(如
DataTable、完整实体类),防止事件链路意外延长生命周期
MyCustomEventArgs 的标准写法长什么样
一个干净、可维护的自定义事件参数类,核心就三件事:继承、只读属性、构造注入。下面这个例子传递用户 ID 和操作类型:
public class UserOperationEventArgs : EventArgs
{
public int UserId { get; }
public string OperationType { get; }
public UserOperationEventArgs(int userId, string operationType)
{
UserId = userId;
OperationType = operationType ?? "Unknown";
}
}
注意:?? 防空、属性无 set、构造函数强制初始化——这三点能挡住大部分运行时隐患。别图省事写成自动属性 public int UserId { get; set; },否则调用方可能误改参数值。
事件声明和触发时怎么关联自定义参数
声明事件时,委托签名的第二个参数必须是你的自定义类型(不是 EventArgs),否则编译报错;触发时用 OnXXX 惯例方法封装,既统一入口又方便子类重写。
- 事件声明:
public event EventHandler<useroperationeventargs> UserOperationCompleted;</useroperationeventargs> - 触发方法通常这样写:
protected virtual void OnUserOperationCompleted(UserOperationEventArgs e) - 内部触发前务必判空:
UserOperationCompleted?.Invoke(this, e) - 不要在
OnXXX方法里 new 参数对象后立刻触发——先构造好再传入,逻辑更清晰,也方便单元测试 mock
订阅方拿到参数后容易忽略的兼容细节
很多人只关注“能取到值”,但实际协作中常踩两个坑:一是参数类没加 [Serializable],跨 AppDomain 或 WCF 场景直接失败;二是属性名变更引发 JSON 序列化/反序列化断裂,尤其用 SignalR 或 API 返回事件快照时。
- 如果项目需支持 .NET Framework 旧版本或远程调用,给参数类加上
[Serializable] - 避免用
dynamic或反射强转参数类型,订阅方法签名应明确写object sender, UserOperationEventArgs e - 若参数类后续要增减字段,优先用可空类型(
int?)或默认值兜底,别让老订阅者因新字段缺失而崩溃
最麻烦的不是写错,而是参数类被当成 DTO 处处传递、又被其他模块反向依赖——它该是事件契约的一部分,不是通用数据模型。划清边界比语法正确更难,也更重要。










