CodeGym /课程 /C# SELF /事件的标准模板 ( EventHandler

事件的标准模板 ( EventHandler/ EventArgs)

C# SELF
第 52 级 , 课程 3
可用

1. 介绍

先回忆一下:事件是一个公开的契约,承诺你的代码在某事发生时会“调用”订阅者。在之前的讲座里你可能见过这样的声明:


public event Action<string> MessageSent;

或者:


public delegate void MyHandler(int value);
public event MyHandler SomethingHappened;

这能工作,但这种做法会带来一堆问题——从处理器签名不统一到无法知道事件来自谁以及发生了什么。想象一下键盘每次按下都随机触发,不同场景不同结果,而还是在同一个程序里。比如你在玩游戏,按空格键,刚才是跳跃,现在却竟然打开了物品栏。噩梦般的不标准!

在 .NET 里有种事件“宪法”——这是一套关于处理器签名和随事件传递信息结构的约定。典型风格长这样:


void Handler(object sender, EventArgs args);

熟悉吧?从 WinForms 的按钮点击到 ASP.NET 的系统事件,甚至第三方库,你都会看到这行签名。

2. 什么是 EventHandlerEventArgs

核心思想:每个事件传递两件事

  • 谁触发了事件? sender
  • 发生了什么? EventArgs — 附加数据

在 C# 里通常这样表达:


public delegate void EventHandler(object sender, EventArgs e);
  • object sender — 指向事件发起者的引用。可以是任意对象,通常是 this
  • EventArgs e — 带事件额外信息的对象。简单场景用 EventArgs.Empty,复杂场景创建自定义继承自 EventArgs 的类型。

趣闻

在 .NET 的官方建议里,公共 API 的事件应采用签名 (object sender, EventArgs e)。如果你看到没有 senderEventArgs 的事件,那很可能是作者简化了,而不是标准的 .NET 风格。

统一标准的巨大好处

有了统一标准,事件更容易记录、测试、以通用方式订阅并扩展系统。打开 WinForms、WPF、ASP.NET 的源码,你会看到相同的模板。

3. 如何使用标准事件模板

1. 使用内置委托 EventHandler

不必声明自己的 delegate,可以直接用现成的:


public event EventHandler SmthHappened;

现在处理器总是长得一样:


private void OnSmthHappened(object sender, EventArgs e)
{
    // 对事件的响应逻辑
}

订阅仍然很简单:


myObj.SmthHappened += OnSmthHappened;

重要:如果事件不传递额外数据,使用 EventArgs.Empty

2. 创建自定义事件参数

如果需要传递信息(计算结果、文件名、错误等),就创建一个继承自 EventArgs 的类型:


public class CalculationEventArgs : EventArgs
{
    public double Result { get; }
    public CalculationEventArgs(double result) => Result = result;
}

然后使用泛型委托 EventHandler<TEventArgs>


public event EventHandler<CalculationEventArgs> CalculationFinished;

处理器现在接收你的具体参数类型:


private void OnCalculationFinished(object sender, CalculationEventArgs e)
{
    Console.WriteLine($"计算完成。结果: {e.Result}");
}

4. 应用示例

扩展我们的教学项目——假设有个计算器,做加减运算,并通过事件通知操作完成。

最简示例


public class Calculator
{
    public event EventHandler<CalculationEventArgs> CalculationPerformed;

    public void Add(int a, int b)
    {
        int result = a + b;
        // 触发事件
        CalculationPerformed?.Invoke(this, new CalculationEventArgs(result));
    }
}

public class CalculationEventArgs : EventArgs
{
    public int Result { get; }
    public CalculationEventArgs(int result) => Result = result;
}

订阅并使用:


var calc = new Calculator();
calc.CalculationPerformed += (sender, e) =>
{
    Console.WriteLine($"操作结果: {e.Result}");
};
calc.Add(10, 20);
// 输出: 操作结果: 30

这就是“标准”:处理器总是接收发送者对象和参数对象——通用且清晰。

5. 有用的细节

封装触发事件的逻辑:好习惯

在 .NET 中通常把触发事件的逻辑放到一个受保护的方法,方法名前缀为 On


protected virtual void OnCalculationPerformed(CalculationEventArgs e)
{
    CalculationPerformed?.Invoke(this, e);
}

在具体逻辑(比如 "Add", "Subtract")里只需调用这个方法:


public void Add(int a, int b) => OnCalculationPerformed(new CalculationEventArgs(a + b));

这种风格允许继承者重写事件触发行为,并减少忘记触发事件的风险。

示意图:按 .NET 标准事件是如何工作的

graph LR
    A[发布对象] -- "event EventHandler/ EventHandler<TEventArgs>" --> B[订阅者列表]
    B -- "处理方法 (object sender, EventArgs e)" --> C[对事件的响应]
    A -- "this (发送者)" --> C
    A -- "EventArgs (数据)" --> C

事件声明方式对比

方式 传递触发者 (sender) 传递参数 通用性 在 .NET 中的使用
public event Action<int> MyEvent;
public delegate void MyHandler(object, int); event MyHandler ...
中等 否(很少)
public event EventHandler;
否 (EventArgs) 是,标准
public event EventHandler<MyArgs>;
是 (MyArgs) 非常高 是,标准

面试和生产代码中的实际价值

使用标准事件模板是 .NET 开发者的必备技能。面试时很可能会问到 EventHandler 和那对 sender/EventArgs。像 Action<T> 这样的事件通常被认为是“简化版”。

在真实项目中,这种做法便于协作、测试、扩展和维护。第三方库(日志、分析器)在遇到标准形式时也更容易集成。

事件触发流程的块状图

flowchart TD
    subgraph A[发布类]
        C1((对象))
        C2[触发事件的方法]
        C3["event EventHandler<MyArgs>"]
    end
    subgraph B[订阅类]
        D1((订阅))
        D2["事件处理器 (object sender, MyArgs args)"]
    end
    C1 -- 调用 --> C2
    C2 -- "Invoke(this, args)" --> C3
    C3 -- "通知" --> D1
    D1 -- "执行" --> D2

6. 建议、细节和常见错误

1. 处理器签名。 想用 Action<int> 类型的事件?虽然诱人,但你会失去 sender 和与生态的一致性。

2. 参数传递。 不要把 EventArgs 和普通参数混淆。把所有传给处理器的数据放到事件参数对象里。

3. 用 null 作为参数。 不要用 null,如果没有额外数据就用 EventArgs.Empty

4. 弱类型化。 不要把所有事件都用一个“万金油” EventHandler,把各类事件做成各自的子类参数——可读性和可靠性更高。

5. 触发事件时报错。 始终检查是否有订阅者: SomeEvent?.Invoke(this, e)。没有订阅者时事件引用为 null

6. 破坏封装。 不要在发布类之外触发事件。事件只用于订阅/退订;触发应在类内部通过 On... 方法完成。

2
任务
C# SELF, 第 52 级, 课程 3
已锁定
使用 EventHandler 和自定义 EventArgs 创建事件
使用 EventHandler 和自定义 EventArgs 创建事件
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION