在 C# 中,**事件(Event)**是一种非常重要的机制,它允许一个对象在状态发生变化时通知其他对象,而无需知道这些对象的具体实现。事件广泛应用于 GUI 编程、系统通知、业务逻辑处理以及线程间通信等场景,是实现观察者模式(Observer Pattern)的核心工具。
本文将系统讲解 C# 事件的概念、使用方式,并结合实例演示实际应用。
事件的基本概念
可以把事件理解为"信号"或"通知",通常由某个操作触发,例如:
- 用户操作:点击按钮、键盘输入、鼠标移动等
- 系统操作:定时器到期、中断触发、业务流程完成等
事件机制允许 发布者(Publisher) 将事件通知给 订阅者(Subscriber),订阅者在事件触发时执行相应的处理逻辑。C# 中事件和委托密不可分,事件实际上是封装了委托的一种机制。
事件的核心流程
使用 C# 事件主要包括四个步骤:
-
声明委托
委托定义了事件处理函数的签名。
csharppublic delegate void NotifyEventHandler(object sender, EventArgs e); -
声明事件
使用
event关键字声明事件,并指定委托类型。csharppublic event NotifyEventHandler ProcessCompleted; -
触发事件
在合适的时机调用事件,通知所有订阅者。
csharpprotected virtual void OnProcessCompleted(EventArgs e) { ProcessCompleted?.Invoke(this, e); } -
订阅和取消订阅事件
其他类通过
+=和-=运算符来订阅或取消订阅事件。csharpprocess.ProcessCompleted += Process_ProcessCompleted;
发布者与订阅者
-
发布者(Publisher)
包含事件和触发逻辑的类。当事件发生时,它调用事件通知订阅者。
-
订阅者(Subscriber)
提供事件处理函数的类。订阅者注册事件后,在事件触发时执行相应操作。
这种模式称为 发布-订阅模式(Publisher-Subscriber Pattern)。
简单示例:业务流程事件
csharp
using System;
namespace EventDemo
{
public delegate void NotifyEventHandler(object sender, EventArgs e);
public class ProcessBusinessLogic
{
public event NotifyEventHandler ProcessCompleted;
protected virtual void OnProcessCompleted(EventArgs e)
{
ProcessCompleted?.Invoke(this, e);
}
public void StartProcess()
{
Console.WriteLine("Process Started!");
// 模拟业务逻辑
OnProcessCompleted(EventArgs.Empty);
}
}
public class EventSubscriber
{
public void Subscribe(ProcessBusinessLogic process)
{
process.ProcessCompleted += Process_ProcessCompleted;
}
private void Process_ProcessCompleted(object sender, EventArgs e)
{
Console.WriteLine("Process Completed!");
}
}
class Program
{
static void Main()
{
ProcessBusinessLogic process = new ProcessBusinessLogic();
EventSubscriber subscriber = new EventSubscriber();
subscriber.Subscribe(process);
process.StartProcess();
}
}
}
运行结果:
Process Started!
Process Completed!
实战示例:热水锅炉日志记录系统
通过一个模拟热水锅炉的系统,展示事件在实际业务中的应用:
csharp
using System;
using System.IO;
class Boiler
{
public int Temp { get; private set; }
public int Pressure { get; private set; }
public Boiler(int temp, int pressure)
{
Temp = temp;
Pressure = pressure;
}
}
class BoilerEventPublisher
{
public delegate void BoilerLogHandler(string message);
public event BoilerLogHandler BoilerEventLog;
public void LogBoilerStatus()
{
Boiler boiler = new Boiler(100, 12);
string remarks = (boiler.Temp < 80 || boiler.Temp > 150 || boiler.Pressure < 12 || boiler.Pressure > 15)
? "Need Maintenance" : "O.K.";
BoilerEventLog?.Invoke(
$"Logging Info:\nTemperature: {boiler.Temp}\nPressure: {boiler.Pressure}\nMessage: {remarks}");
}
}
class BoilerLogger : IDisposable
{
private readonly StreamWriter _writer;
public BoilerLogger(string filePath)
{
_writer = new StreamWriter(filePath, append: true);
}
public void LogToFile(string info) => _writer.WriteLine(info);
public void Dispose() => _writer?.Close();
}
class Program
{
static void LoggerToConsole(string info) => Console.WriteLine(info);
static void Main()
{
using var fileLogger = new BoilerLogger("boiler.txt");
var boilerEvent = new BoilerEventPublisher();
// 订阅事件
boilerEvent.BoilerEventLog += LoggerToConsole;
boilerEvent.BoilerEventLog += fileLogger.LogToFile;
// 触发事件
boilerEvent.LogBoilerStatus();
}
}
运行效果:
Logging Info:
Temperature: 100
Pressure: 12
Message: O.K.
日志同时也写入 boiler.txt 文件,实现事件驱动的多通道日志记录。
使用事件的注意事项
- 封装性:外部类只能订阅或取消订阅事件,不能直接触发事件,保证发布者控制权。
- 线程安全 :在多线程环境中触发事件,使用
?.Invoke或委托拷贝,避免空引用或竞态条件。 - 自定义事件参数 :当需要传递更多信息时,可继承
EventArgs创建自定义事件参数类,而不仅仅使用EventArgs.Empty。
总结
C# 事件是封装了委托的通知机制,非常适合实现发布-订阅模式。通过事件,可以让对象在状态变化时通知多个订阅者,实现业务逻辑解耦。使用事件的基本流程:声明委托 → 声明事件 → 触发事件 → 订阅事件。事件不仅适用于 UI 操作,还能用于后台业务逻辑、日志记录、线程通信等场景。掌握事件机制及注意事项,你的 C# 程序将更加灵活、可扩展且易于维护。