news 2026/6/8 6:46:08

委托/事件

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
委托/事件

一、委托:方法的 “容器”,实现代码的解耦

委托(Delegate)可以理解为方法的 “类型安全指针”,它定义了方法的签名(返回值 + 参数列表),可以用来封装、传递具有相同签名的方法,让方法调用和具体实现解耦。

1. 委托的核心定义

  • 对具有相同返回类型和参数列表的方法进行封装,是一种 “引用方法” 的数据类型。
  • 核心价值:实现代码的松耦合设计,让调用者不需要关心方法的具体实现,只需要遵循委托的签名即可。
  • 支持多播:一个委托实例可以绑定多个方法,调用时会按顺序执行所有绑定的方法。

2. 委托的语法与调用

(1)定义委托

语法格式非常固定:

// 定义委托:public delegate 返回值类型 委托名(参数列表); public delegate void MyDelegate(string message);

这行代码就定义了一个委托,它只能接收 “无返回值、参数为 string” 的方法。

(2)调用委托

委托有两种调用方式,对应同步和异步场景:

  • 同步调用:两种写法等价
    1. 直接调用委托实例:myDelegate("Hello");
    2. 调用Invoke()方法:myDelegate.Invoke("Hello");
  • 异步调用:两种实现方式
    1. 传统模式:BeginInvoke()启动异步调用 +EndInvoke()等待结果
    2. 现代推荐:使用Task封装异步委托,更简洁易读。
(3)多播委托:绑定多个方法

多播委托支持通过+=绑定多个方法,-=解绑方法,调用时会按绑定顺序依次执行:

// 绑定多个方法 MyDelegate del = Method1; del += Method2; del += Method3; // 调用时会依次执行Method1、Method2、Method3 del("多播委托测试"); // 解绑方法 del -= Method2;

3. 委托的核心作用

委托的本质就是解耦方法调用与实现:举个例子,你写了一个日志工具类,它不需要关心谁来记录日志,只需要接收一个符合签名的委托方法即可,业务层可以自由实现 “写文件日志”“写数据库日志”“控制台打印日志”,而日志工具类完全不用修改代码,完美实现了开闭原则。


二、事件:基于委托的 “发布 - 订阅” 模型

事件(Event)是在委托的基础上,封装了一层 “发布 - 订阅” 的安全壳,专门用于实现通知机制,比如 WinForm 里的按钮点击、文本框变更,本质都是事件驱动。

1. 事件的核心定义

事件是一种受限的委托,它遵循 “发布者 - 订阅者” 模式:

  • 发布者:触发事件的对象,只负责 “通知发生了”,不关心谁订阅了事件。
  • 订阅者:响应事件的对象,只需要注册事件处理方法,不需要关心发布者的内部实现。这种模式天然实现了模块之间的解耦,是 UI 交互、状态通知的核心。

2. 事件的语法实现

(1)定义事件:三种常用方式
  1. 直接使用event关键字封装委托
    // 先定义委托 public delegate void ClickHandler(object sender, EventArgs e); // 定义事件 public event ClickHandler Click;
  2. 使用预定义的EventHandler委托(通用场景)
    // 系统预定义的无参数事件委托,直接使用 public event EventHandler Click;
  3. 自定义事件参数:使用EventHandler<TEventArgs>当需要传递自定义数据时,先定义EventArgs的子类,再使用泛型委托:
    // 自定义事件参数,继承EventArgs public class ButtonClickEventArgs : EventArgs { public string ButtonName { get; set; } } // 定义事件 public event EventHandler<ButtonClickEventArgs> Click;
(2)触发事件

发布者在满足条件时触发事件,需要先判断事件是否为null(即是否有订阅者):

protected virtual void OnClick(ButtonClickEventArgs e) { // 触发事件,sender是发布者自身,e是事件参数 Click?.Invoke(this, e); }
(3)订阅事件:多种实现方式
  1. 最常用:直接用+=绑定方法
    // 订阅按钮点击事件 button.Click += Button_Click; private void Button_Click(object sender, EventArgs e) { Console.WriteLine("按钮被点击了!"); }
  2. 其他场景:
    • 基类中的事件:子类可以直接重写触发方法(如 WinForm 的OnClick
    • 接口中定义事件:让不同的类实现统一的事件通知规范
    • 自定义方法访问器:用add/remove关键字自定义事件的订阅和解绑逻辑

3. 事件的典型使用场景

  • WinForm/WPF UI 交互:按钮点击、文本变更、窗口加载等,是事件最经典的应用场景。
  • 模块解耦:比如业务逻辑层触发数据变更事件,UI 层订阅并更新界面,二者完全解耦。
  • 日志 / 监控 / 状态通知:比如服务状态变更时,触发事件通知日志模块、监控模块执行对应操作。

三、事件与委托的异同:别再傻傻分不清了

很多人学完委托和事件,还是搞不清二者的区别,其实它们是 “基础与封装” 的关系,核心异同点整理如下:

表格

维度委托事件
相同点1. 底层基于委托机制实现2. 都支持多播(+=/-=绑定方法)3. 核心用途都是回调、通知、解耦同左
不同点:访问控制可以被外部直接调用(任何持有委托对象的代码都能调用)只能由定义事件的类内部触发,外部只能通过+=/-=订阅 / 解绑,不能直接调用,更安全
不同点:封装性只是一个方法指针类型,没有额外的封装限制隐式生成私有委托字段,外部无法直接访问底层委托,避免被恶意调用或清空
不同点:接口支持不能在接口中定义委托可以在接口中定义事件,比如IButton接口定义Click事件,不同按钮类实现接口即可
不同点:使用场景更通用,适合需要传递方法、异步调用、简单回调的场景专门用于发布 - 订阅的通知场景,比如 UI 事件、状态变更通知

简单总结一句话:事件是 “安全版的委托”,它限制了委托的调用权限,只允许发布者内部触发,外部只能订阅,更适合事件驱动的场景;而委托更灵活,适合需要直接传递和调用方法的场景。


四、实战小例子:用事件实现一个简单的按钮点击

结合上面的知识点,写一个极简的按钮事件 Demo,直观感受事件的使用:

using System; // 自定义按钮类 public class MyButton { // 定义事件,使用泛型EventHandler public event EventHandler<string> Click; // 模拟按钮点击,触发事件 public void SimulateClick() { Console.WriteLine("按钮被点击,准备触发事件..."); // 触发事件,传递按钮名称 Click?.Invoke(this, "提交按钮"); } } // 订阅者:日志模块 public class LogModule { public void OnButtonClick(object sender, string buttonName) { Console.WriteLine($"日志记录:{buttonName} 被点击了"); } } // 订阅者:业务处理模块 public class BusinessModule { public void OnButtonClick(object sender, string buttonName) { Console.WriteLine($"业务处理:{buttonName} 点击,准备提交数据"); } } class Program { static void Main(string[] args) { // 发布者 MyButton button = new MyButton(); // 订阅者 LogModule log = new LogModule(); BusinessModule business = new BusinessModule(); // 订阅事件 button.Click += log.OnButtonClick; button.Click += business.OnButtonClick; // 模拟点击,触发所有订阅的方法 button.SimulateClick(); Console.ReadLine(); } }

运行结果:

按钮被点击,准备触发事件... 日志记录:提交按钮 被点击了 业务处理:提交按钮 点击,准备提交数据

可以看到,按钮类完全不用关心有多少订阅者,只需要触发事件即可,订阅者也只需要实现自己的处理逻辑,二者完全解耦,这就是事件的魅力。


五、总结

委托和事件是 C# 中实现面向对象设计、事件驱动架构的核心,理解它们不仅能帮你写出更解耦、更易维护的代码,也是学习异步编程、框架底层(比如ASP.NET Core 的管道、WPF 的依赖属性)的基础。

记住两个核心要点:

  1. 委托是 “方法的容器”,负责封装和传递方法,实现松耦合。
  2. 事件是 “安全的委托封装”,专门用于发布 - 订阅的通知场景,限制了调用权限,更安全。

掌握了这两个概念,你会发现很多以前看不懂的框架代码,瞬间就通了!

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/8 6:46:02

D. Friends and the Restaurant

time limit per test2 secondsmemory limit per test256 megabytesA group of n friends decide to go to a restaurant. Each of the friends plans to order meals for xi burles and has a total of yi burles (1≤i≤n).The friends decide to split their visit to the re…

作者头像 李华
网站建设 2026/6/8 6:45:18

玩转SSD1306的8种扫描模式:用Arduino实现OLED动画和特效显示

玩转SSD1306的8种扫描模式&#xff1a;用Arduino实现OLED动画和特效显示在创客和电子爱好者的世界里&#xff0c;OLED显示屏因其高对比度、低功耗和快速响应等特性&#xff0c;成为项目展示的理想选择。而SSD1306作为驱动这类显示屏的常用控制器&#xff0c;其功能远比我们想象…

作者头像 李华
网站建设 2026/6/8 6:42:43

Circle Loss超参数调优指南:如何在你的自定义数据集上找到最优的γ和m?

Circle Loss超参数调优实战&#xff1a;从理论到业务落地的γ与m选择策略当你在商品图像检索系统中发现模型对相似款式的区分度不足&#xff0c;或在声纹识别任务中遇到同类声音特征分散的问题时&#xff0c;Circle Loss的两个神秘参数γ和m往往成为破局关键。不同于传统损失函…

作者头像 李华
网站建设 2026/6/8 6:42:40

避坑指南:Apple Pay订阅续期与服务端状态同步的那些事儿(Java版)

避坑指南&#xff1a;Apple Pay订阅续期与服务端状态同步的那些事儿&#xff08;Java版&#xff09;订阅型商品在移动应用生态中扮演着重要角色&#xff0c;但相比一次性购买&#xff0c;自动续期订阅的后端实现复杂度呈指数级上升。作为Java后端工程师&#xff0c;我们不仅要处…

作者头像 李华