跳转到内容

C Sharp 事件基础

来自代码酷
Admin留言 | 贡献2025年4月29日 (二) 18:40的版本 (Page creation by admin bot)

(差异) ←上一版本 | 已核准修订 (差异) | 最后版本 (差异) | 下一版本→ (差异)

C#事件基础[编辑 | 编辑源代码]

介绍[编辑 | 编辑源代码]

在C#中,事件(Event)是一种特殊的成员,它允许类或对象在特定动作发生时通知其他类或对象。事件基于委托(Delegate)机制,是观察者设计模式的实现方式之一,广泛用于实现松耦合的组件间通信。

事件的核心特点包括:

  • 发布-订阅模型:事件源(发布者)触发事件,事件处理程序(订阅者)响应事件
  • 类型安全:事件基于委托,确保只有符合签名的方法能被订阅
  • 封装性:外部代码只能订阅/取消订阅,不能直接触发事件

事件的基本结构[编辑 | 编辑源代码]

事件需要三个关键组件:

  1. 事件委托:定义事件处理方法的签名
  2. 事件声明:使用`event`关键字
  3. 事件触发方法:在适当条件下调用事件

语法示例[编辑 | 编辑源代码]

// 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

事件流程[编辑 | 编辑源代码]

sequenceDiagram participant Publisher as 发布者 participant Subscriber as 订阅者 Publisher->>Subscriber: 1. 订阅事件 (+=) loop 事件监控 Publisher->>Publisher: 2. 检测状态变化 alt 条件满足 Publisher->>Subscriber: 3. 触发事件 end end Publisher->>Subscriber: 4. 取消订阅 (-=)

实际应用案例[编辑 | 编辑源代码]

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`的类,包含需要传递的数据。