C# 事件(Event)核心概念

文章目录

  • 前言
  • [‌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# 提供了一种类型安全、松耦合的方式来实现对象间的通信,是构建响应式应用程序的核心机制。

相关推荐
zh_xuan10 分钟前
c++ 单例模式
开发语言·c++·单例模式
老胖闲聊35 分钟前
Python Copilot【代码辅助工具】 简介
开发语言·python·copilot
Blossom.11839 分钟前
使用Python和Scikit-Learn实现机器学习模型调优
开发语言·人工智能·python·深度学习·目标检测·机器学习·scikit-learn
曹勖之1 小时前
基于ROS2,撰写python脚本,根据给定的舵-桨动力学模型实现动力学更新
开发语言·python·机器人·ros2
豆沙沙包?2 小时前
2025年- H77-Lc185--45.跳跃游戏II(贪心)--Java版
java·开发语言·游戏
军训猫猫头2 小时前
96.如何使用C#实现串口发送? C#例子
开发语言·c#
liuyang-neu2 小时前
java内存模型JMM
java·开发语言
不爱写代码的玉子3 小时前
HALCON透视矩阵
人工智能·深度学习·线性代数·算法·计算机视觉·矩阵·c#
我很好我还能学4 小时前
【面试篇 9】c++生成可执行文件的四个步骤、悬挂指针、define和const区别、c++定义和声明、将引用作为返回值的好处、类的四个缺省函数
开发语言·c++
蓝婷儿4 小时前
6个月Python学习计划 Day 16 - 面向对象编程(OOP)基础
开发语言·python·学习