C Sharp 事件基础
外观
C#事件基础[编辑 | 编辑源代码]
介绍[编辑 | 编辑源代码]
在C#中,事件(Event)是一种特殊的成员,它允许类或对象在特定动作发生时通知其他类或对象。事件基于委托(Delegate)机制,是观察者设计模式的实现方式之一,广泛用于实现松耦合的组件间通信。
事件的核心特点包括:
- 发布-订阅模型:事件源(发布者)触发事件,事件处理程序(订阅者)响应事件
- 类型安全:事件基于委托,确保只有符合签名的方法能被订阅
- 封装性:外部代码只能订阅/取消订阅,不能直接触发事件
事件的基本结构[编辑 | 编辑源代码]
事件需要三个关键组件:
- 事件委托:定义事件处理方法的签名
- 事件声明:使用`event`关键字
- 事件触发方法:在适当条件下调用事件
语法示例[编辑 | 编辑源代码]
// 1. 定义委托
public delegate void EventHandlerDelegate(string message);
// 2. 包含事件的类
public class EventPublisher
{
// 3. 声明事件
public event EventHandlerDelegate OnEventTriggered;
// 4. 触发事件的方法
public void TriggerEvent(string message)
{
// 检查是否有订阅者
OnEventTriggered?.Invoke(message);
}
}
// 使用示例
public class Program
{
public static void Main()
{
var publisher = new EventPublisher();
// 订阅事件
publisher.OnEventTriggered += HandleEvent;
// 触发事件
publisher.TriggerEvent("Hello, Event!");
// 取消订阅
publisher.OnEventTriggered -= HandleEvent;
}
// 事件处理方法
public static void HandleEvent(string message)
{
Console.WriteLine($"事件收到: {message}");
}
}
输出结果:
事件收到: Hello, Event!
.NET内置事件模式[编辑 | 编辑源代码]
.NET框架提供了标准的事件模式:
- 使用`EventHandler`或`EventHandler<TEventArgs>`委托
- 事件参数应继承自`EventArgs`
标准示例[编辑 | 编辑源代码]
public class TemperatureMonitor
{
// 使用标准EventHandler<T>委托
public event EventHandler<TemperatureChangedEventArgs> TemperatureChanged;
private double currentTemp;
public double CurrentTemperature
{
get => currentTemp;
set
{
if (currentTemp != value)
{
currentTemp = value;
OnTemperatureChanged(new TemperatureChangedEventArgs(value));
}
}
}
protected virtual void OnTemperatureChanged(TemperatureChangedEventArgs e)
{
TemperatureChanged?.Invoke(this, e);
}
}
// 自定义事件参数
public class TemperatureChangedEventArgs : EventArgs
{
public double NewTemperature { get; }
public TemperatureChangedEventArgs(double newTemp)
{
NewTemperature = newTemp;
}
}
// 使用示例
public class Program
{
public static void Main()
{
var monitor = new TemperatureMonitor();
monitor.TemperatureChanged += Monitor_TemperatureChanged;
monitor.CurrentTemperature = 25.5; // 会触发事件
monitor.CurrentTemperature = 30.0; // 会触发事件
}
private static void Monitor_TemperatureChanged(object sender, TemperatureChangedEventArgs e)
{
Console.WriteLine($"温度变更为: {e.NewTemperature}°C");
}
}
输出结果:
温度变更为: 25.5°C 温度变更为: 30°C
事件流程[编辑 | 编辑源代码]
实际应用案例[编辑 | 编辑源代码]
UI按钮点击事件[编辑 | 编辑源代码]
在Windows Forms或WPF中,按钮点击是最常见的事件应用:
// Windows Forms示例
public class Form1 : Form
{
private Button button1;
public Form1()
{
button1 = new Button { Text = "点击我" };
button1.Click += Button1_Click; // 订阅事件
Controls.Add(button1);
}
private void Button1_Click(object sender, EventArgs e)
{
MessageBox.Show("按钮被点击了!");
}
}
自定义文件监视器[编辑 | 编辑源代码]
实现一个简单的文件变更通知系统:
public class FileWatcher
{
public event EventHandler<FileChangedEventArgs> FileChanged;
public void Watch(string filePath)
{
var lastWriteTime = File.GetLastWriteTime(filePath);
while (true)
{
Thread.Sleep(1000);
var currentWriteTime = File.GetLastWriteTime(filePath);
if (currentWriteTime != lastWriteTime)
{
lastWriteTime = currentWriteTime;
OnFileChanged(new FileChangedEventArgs(filePath, currentWriteTime));
}
}
}
protected virtual void OnFileChanged(FileChangedEventArgs e)
{
FileChanged?.Invoke(this, e);
}
}
public class FileChangedEventArgs : EventArgs
{
public string FilePath { get; }
public DateTime ChangeTime { get; }
public FileChangedEventArgs(string path, DateTime changeTime)
{
FilePath = path;
ChangeTime = changeTime;
}
}
高级主题[编辑 | 编辑源代码]
事件访问器[编辑 | 编辑源代码]
可以自定义事件的添加/移除行为:
private EventHandler _myEvent;
public event EventHandler MyEvent
{
add
{
Console.WriteLine("添加订阅");
_myEvent += value;
}
remove
{
Console.WriteLine("移除订阅");
_myEvent -= value;
}
}
线程安全的事件触发[编辑 | 编辑源代码]
在多线程环境中安全触发事件:
protected virtual void OnTemperatureChanged(TemperatureChangedEventArgs e)
{
var temp = TemperatureChanged; // 获取本地副本
temp?.Invoke(this, e); // 触发本地副本
}
最佳实践[编辑 | 编辑源代码]
1. 使用`EventHandler<T>`而非自定义委托类型,除非有特殊需求 2. 事件命名使用动词或动词短语(如`Clicked`、`Changed`) 3. 总是检查事件是否为null再触发 4. 在类内部使用受保护的虚方法触发事件(如`OnEventName`模式) 5. 考虑线程安全性,特别是在多线程环境中
常见问题[编辑 | 编辑源代码]
Q: 事件和委托有什么区别? A: 委托是类型,事件是基于委托的成员。事件提供了更好的封装性,外部代码只能订阅/取消订阅,不能直接调用或覆盖事件。
Q: 为什么事件触发前要检查null? A: 如果没有订阅者,事件为null,直接调用会导致NullReferenceException。使用`?.`运算符可以安全触发。
Q: 如何传递自定义数据? A: 创建继承自`EventArgs`的类,包含需要传递的数据。