文章目录
- 前言
- [1. 事件的核心概念](#1. 事件的核心概念)
- [2. 事件的声明与使用](#2. 事件的声明与使用)
- [3. 标准事件模式(EventHandler 和 EventArgs)](#3. 标准事件模式(EventHandler 和 EventArgs))
- [4. 事件与委托的区别](#4. 事件与委托的区别)
- [5. 事件的使用场景](#5. 事件的使用场景)
- [6. 高级特性](#6. 高级特性)
- [7. 注意事项](#7. 注意事项)
- [8. 完整示例:温度监控系统](#8. 完整示例:温度监控系统)
- [9. 常见问题](#9. 常见问题)
前言
在 C# 中,事件(Event) 是基于委托(Delegate)的机制,用于实现发布-订阅(Publish-Subscribe)模式。事件允许对象(发布者)通知其他对象(订阅者)某个特定动作已发生(如按钮点击、数据更新)。以下是事件的详细讲解:
1. 事件的核心概念
- 发布者(Publisher):触发事件的对象(如按钮控件)。
- 订阅者(Subscriber):响应事件的对象(如事件处理方法)。
- 事件委托(Event Delegate):定义事件的签名(参数和返回值)。
- 封装性:事件对外部仅暴露 +=(订阅)和 -=(取消订阅),无法直接触发或覆盖。
2. 事件的声明与使用
(1) 声明事件
csharp
public class Button
{
// 1. 定义委托类型(约定返回 void,参数为 object 和 EventArgs 派生类)
public delegate void ClickEventHandler(object sender, EventArgs e);
// 2. 声明事件(基于委托)
public event ClickEventHandler? Clicked;
// 3. 触发事件的方法(protected virtual 以便派生类重写)
protected virtual void OnClicked(EventArgs e)
{
Clicked?.Invoke(this, e); // 安全调用(若没有订阅者为 null)
}
// 4. 外部触发的入口(如用户点击按钮)
public void Click()
{
OnClicked(EventArgs.Empty); // 触发事件
}
}
(2) 订阅事件
csharp
public class Program
{
public static void Main()
{
Button button = new Button();
// 订阅事件(通过 +=)
button.Clicked += Button_Clicked;
button.Clicked += (sender, e) => Console.WriteLine("Lambda 表达式处理点击事件");
// 取消订阅(通过 -=)
// button.Clicked -= Button_Clicked;
}
// 事件处理方法
private static void Button_Clicked(object sender, EventArgs e)
{
Console.WriteLine("按钮被点击!");
}
}
3. 标准事件模式(EventHandler 和 EventArgs)
.NET 提供了标准委托类型 EventHandler 和基类 EventArgs,避免重复定义委托:
csharp
// 标准事件声明
public event EventHandler? Clicked; // 等价于 EventHandler<EventArgs>
// 自定义事件参数
public class TemperatureChangedEventArgs : EventArgs
{
public double OldTemperature { get; }
public double NewTemperature { get; }
public TemperatureChangedEventArgs(double oldTemp, double newTemp)
{
OldTemperature = oldTemp;
NewTemperature = newTemp;
}
}
// 使用泛型 EventHandler<T>
public event EventHandler<TemperatureChangedEventArgs>? TemperatureChanged;
4. 事件与委托的区别
特性 | 委托(Delegate) | 事件(Event) |
---|---|---|
访问权限 | 可被外部直接调用或赋值 | 仅允许 += 和 -= 操作 |
封装性 | 低(暴露委托实例) | 高(隐藏触发逻辑) |
用途 | 通用回调机制 | 特定于发布-订阅场景 |
5. 事件的使用场景
- UI 交互:按钮点击、文本框输入、窗口关闭。
- 数据通知:属性值变化、定时器触发。
- 异步操作:文件下载完成、网络请求响应。
- 插件系统:动态加载模块的事件响应。
6. 高级特性
(1) 自定义事件访问器(add/remove)
可自定义事件的订阅和取消订阅逻辑:
csharp
private EventHandler? _clicked;
public event EventHandler Clicked
{
add
{
_clicked += value;
Console.WriteLine("事件被订阅");
}
remove
{
_clicked -= value;
Console.WriteLine("事件被取消订阅");
}
}
(2) 线程安全的事件触发
在多线程场景中,需确保事件触发安全:
csharp
// 复制委托引用,避免触发时订阅者被修改
EventHandler? handler = TemperatureChanged;
if (handler != null)
{
handler(this, e); // 直接调用,非空时触发
}
7. 注意事项
- 内存泄漏:若订阅者是对象方法,需及时取消订阅(否则对象无法被 GC 回收)。
- 空事件检查:触发事件前检查是否为 null(无订阅者)。
- 事件命名:事件名通常以动词过去式命名(如 Clicked、DataLoaded)。
- 避免长时间阻塞:事件处理应快速完成,避免阻塞发布者线程。
8. 完整示例:温度监控系统
csharp
public class TemperatureSensor
{
private double _currentTemperature;
public event EventHandler<TemperatureChangedEventArgs>? TemperatureChanged;
public double CurrentTemperature
{
get => _currentTemperature;
set
{
if (_currentTemperature != value)
{
var oldTemp = _currentTemperature;
_currentTemperature = value;
OnTemperatureChanged(oldTemp, value);
}
}
}
protected virtual void OnTemperatureChanged(double oldTemp, double newTemp)
{
TemperatureChanged?.Invoke(this, new TemperatureChangedEventArgs(oldTemp, newTemp));
}
}
// 订阅者
public class Display
{
public void Subscribe(TemperatureSensor sensor)
{
sensor.TemperatureChanged += HandleTemperatureChange;
}
private void HandleTemperatureChange(object? sender, TemperatureChangedEventArgs e)
{
Console.WriteLine($"温度从 {e.OldTemperature}℃ 变更为 {e.NewTemperature}℃");
}
}
// 使用
var sensor = new TemperatureSensor();
var display = new Display();
display.Subscribe(sensor);
sensor.CurrentTemperature = 25.5; // 触发事件
9. 常见问题
1:为什么事件通常定义为 virtual?
- 允许派生类重写事件触发逻辑(如添加日志或验证)。
2:如何传递自定义数据?
- 继承 EventArgs 并定义包含数据的派生类(如 TemperatureChangedEventArgs)。
3:事件和观察者模式的关系?
- 事件是观察者模式在 C# 中的实现,发布者相当于 Subject,订阅者相当于 Observer。
通过事件,C# 提供了一种类型安全、松耦合的方式来实现对象间的通信,是构建响应式应用程序的核心机制。