CodeGym /课程 /C# SELF /订阅事件和创建处理程序

订阅事件和创建处理程序

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

1. 订阅事件的语法

想象一个任务管理应用:当任务完成时会触发一个事件,处理程序可以比如发送通知、更新 UI 或写日志。C# 让事件订阅既方便又安全:你明确表示哪个处理程序响应哪个事件。

在 C# 里订阅事件就像在聚会上把自己加入来宾名单:

publisher.MyEvent += HandlerMethod;

这里 publisher 是声明了事件 MyEvent 的对象,HandlerMethod 是在事件发生时会被调用的方法。

举个最小化的例子。假设我们有一个用于记录点击次数的程序:

public class Clicker
{
    public event Action Clicked;

    public void Click()
    {
        // 有东西被点击了!
        Clicked?.Invoke();
    }
}

这是我们的事件发布者 Clicked。现在来订阅一个处理程序:

Clicker clicker = new Clicker();

void OnClicked()
{
    Console.WriteLine("按钮被按下了!");
}

clicker.Clicked += OnClicked;

// 在某处代码里
clicker.Click();
// → "按钮被按下了!"

它是怎么工作的?我们把方法 OnClicked 加到“点击”事件上,每次发生点击时它就会被调用。

2. 事件处理程序:有哪些以及如何声明

事件处理程序就是在事件触发时会被调用的方法。它的签名必须和事件使用的委托类型匹配。例如,如果事件被声明为 public event Action Clicked;, 那么处理程序应该是一个没有参数且没有返回值的方法。

用于 Action 的处理程序

void OnClicked() 
{
    Console.WriteLine("事件发生了 (Action)!");
}

用于标准 EventHandler 的处理程序

当你用传统的 EventHandler 时,处理程序接受两个参数:发送者(object sender)和事件数据(EventArgs e):

public class Alarm
{
    public event EventHandler AlarmRaised;

    public void RaiseAlarm()
    {
        AlarmRaised?.Invoke(this, EventArgs.Empty);
    }
}

Alarm alarm = new Alarm();

void AlarmHandler(object sender, EventArgs e)
{
    Console.WriteLine("警报触发了!");
}

alarm.AlarmRaised += AlarmHandler;
alarm.RaiseAlarm();

匿名方法和 lambda 表达式

C# 允许在订阅时直接使用匿名函数和 lambda 表达式:

clicker.Clicked += () => Console.WriteLine("另一次点击!");

如果事件带参数,可以写得稍复杂一点:

alarm.AlarmRaised += (sender, e) =>
{
    Console.WriteLine($"Alarm raised by: {sender}");
};

4. 有用的细节

订阅和取消订阅:重要细节

现实情况是:聚会来得太多,或者有人想回家。对委托也是一样:处理程序可以添加(订阅)也可以移除(取消订阅):

// 订阅
publisher.MyEvent += MyHandler;

// 取消订阅(当处理程序不再需要时)
publisher.MyEvent -= MyHandler;

为啥重要?如果不取消订阅,尤其是在大型应用里,处理程序可能会一直“挂着”,阻止垃圾回收清理对象 —— 这会导致内存泄漏。

多个处理程序

一个事件可以有任意多个处理程序。它们会按添加顺序依次被调用。

clicker.Clicked += () => Console.WriteLine("第一个处理程序!");
clicker.Clicked += () => Console.WriteLine("第二个处理程序!");

clicker.Click();
// → 第一个处理程序!
// → 第二个处理程序!

你甚至可以在运行时随时订阅或取消订阅处理程序 — 很灵活。

这为什么很重要:真实场景

  • UI (Windows Forms, WPF, WinUI, MAUI): 按钮点击、文本框内容变化——这些都是事件。
  • FileSystemWatcher: 通知文件夹中新文件的出现。
  • 异步操作: 文件下载完成、任务进度更新。
  • 插件系统: 独立模块订阅主应用的事件。

事件模型让架构可扩展:你可以添加新模块并订阅现有事件,而不用改动核心代码。

订阅示例

事件签名 订阅示例 处理程序示例
event Action
ev += Handler;
void Handler() { ... }
event Action<int>
ev += (x) => { ... };
void Handler(int x) { ... }
event EventHandler
ev += Handler;
void Handler(object s, EventArgs e) { ... }
event EventHandler<CustomArgs>
ev += Handler;
void Handler(object s, CustomArgs e) { ... }

5. 常见错误和坑

错误 №1:处理程序签名不匹配。
如果事件声明为 event Action<int>,但你试图订阅一个无参数的方法,编译器会报错。始终检查方法是否符合事件要求的签名。

错误 №2:lambda 捕获变量。
通过 lambda 订阅时,它可能会捕获外部作用域的变量(见“闭包”主题)。如果在订阅后变量发生变化,处理程序会看到变化后的值,这可能导致意外行为。

错误 №3:订阅未初始化的事件源。
如果你在包含该事件的对象还没创建或初始化时就订阅它,可能会遇到 NullReferenceException。订阅前确保对象已准备好。

错误 №4:同一处理程序多次订阅。
如果同一个处理程序被多次订阅,它会被调用同样多次。这不是 bug,是事件的工作方式,但常常会让人意外。

2
任务
C# SELF, 第 52 级, 课程 2
已锁定
多个事件订阅者
多个事件订阅者
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION