C# 一文读懂委托与事件

news/2025/3/18 17:04:02/
什么是委托 delegate

诞生:技术点出来的原因就是 有一些需要将方法作为入参 处理的情况 比如 业务处理完需要回调函数 那就需要传函数入参
委托是C#中的一种类型安全函数指针,允许将方法作为参数传递或存储。它本质上是面向对象的,对方法签名抽象封装(参数列表和返回类型),并支持多播(绑定多个方法)。

所以 当说到委托 脑海里就是 对应的 方法抽象类 使用就是 普通的class类里去声明去掉用当作成员使用

关键特点:

类型安全:编译时检查方法签名,避免非法调用。
​动态绑定:运行时决定调用的方法,支持灵活的回调机制。
​多播性:通过+=和-=操作符链式调用多个方法

如何使用委托 进行实例化

很多种 下面会从复杂到简化版本介绍,但是流程基本都是 1 定义委托(方法参数声明) 2 实例化对应方法的实现 3使用

  1. ​传统 new 关键字实例化
    语法:委托类型 实例名 = new 委托类型(方法名);
public delegate void PrintDelegate(string message);
public static void PrintToConsole(string msg) => Console.WriteLine(msg);// 实例化
PrintDelegate print = new PrintDelegate(PrintToConsole);
print("Hello!"); // 输出:Hello!

特点:
C# 1.0 的经典语法,需显式指定方法名。
适用于需要明确绑定静态方法或实例方法的场景。

  1. ​简写方法名赋值
    语法:委托类型 实例名 = 方法名;
PrintDelegate print = PrintToConsole; // 无需 new 关键字
print("Hello!"); 

特点:
语法更简洁,编译器自动推导委托类型。
推荐在代码可读性要求高时使用。

  1. ​匿名方法(C# 2.0+)​
    语法:
    csharp
    委托类型 实例名 = delegate(参数列表) { /* 方法体 */ };
PrintDelegate print = delegate(string msg) {Console.WriteLine($"匿名方法输出:{msg}");
};
print("Test"); // 输出:匿名方法输出:Test

特点:

无需单独定义方法,直接内联实现逻辑。
适用于简单的一次性操作或临时逻辑。

  1. ​Lambda 表达式(C# 3.0+)​
    语法:委托类型 实例名 = (参数列表) => { /* 方法体 */ };
PrintDelegate print = msg => Console.WriteLine($"Lambda 输出:{msg}");
print("Hello"); // 输出:Lambda 输出:Hello

特点:
语法最简洁,支持表达式体和语句块。
广泛用于 LINQ、事件处理和异步编程。

  1. ​内置泛型委托(Func/Action)​
    语法:

Func<参数类型…, 返回类型> 实例名 = 方法或Lambda;
Action<参数类型…> 实例名 = 方法或Lambda;

// Func(有返回值)
Func<int, int, int> add = (a, b) => a + b;
Console.WriteLine(add(3, 5)); // 输出:8// Action(无返回值)
Action<string> log = s => Console.WriteLine($"日志:{s}");
log("操作完成");

特点:
无需自定义委托类型,直接使用 Func 或 Action。
Func 最多支持 16 个参数和 1 个返回值,Action 无返回值。

  1. ​多播委托(链式调用)​
    语法:

委托类型 实例名 += 方法1;
实例名 += 方法2;

PrintDelegate printChain = PrintToConsole;
printChain += msg => Console.WriteLine($"第二条输出:{msg}");printChain("多播测试"); 
// 输出:
// Hello!
// 第二条输出:多播测试

多播委托 就是方法链 就是+= 增加或-= 减少委托的方法
但是注意

  1. -=只能是移除同一个实例的 顺序 是 +123 -321
  2. 方法链返回的只是最后一个方法返回 都是string内容 只返回最后一个
  3. 如果中间出错 不捕获异常 就中断了 捕获异常 后续也不执行
    在这里插入图片描述
使用委托的目的: 解耦逻辑 扩展 异步回调

还是面向对象的 但是 对 方法的抽象 解耦 对内公用 对外扩展的一个封装

比如 猫叫 狗跑 鸡飞 业务逻辑等等 正常就是 调用这三个方法在逻辑中
解耦就吧这三个方法写入委托中 在执行逻辑中只需调用委托不关心 具体的方法了

总结就是 委托就是 提炼方法来代替 参数业务逻辑处理 从而达到 解耦,新业务就新方法 ,其次是 委托的这些方法有公共逻辑可以 整合到委托中 无需在各个方法中 在写一遍

委托 协变 与 逆变

协变 返回值 派生类–》父类 (委托定义)
逆变 入参 父类–》派生类 (委托定义)

什么是事件 有了委托还要事件干嘛? 加了权限的委托 ,安全上配合发布订阅使用 观察者模式

事件是基于委托的发布-订阅模式,用于对象间通信。它封装了委托的调用权限,确保外部代码只能通过+=和-=订阅或取消订阅。
就是特殊的委托 加了权限的委托 只允许在声明的类内部 进行invoke 哪怕子类也不行 外部调用也不行
1.比委托更加安全 相当于更完善,委托是public 可能出错的概率大 比如不小心在外部影响 事件是 private 只在内部 2. 可使用 发布订阅模式异步的方式 以及更好解耦扩展 委托本身而言是 同步的机制 虽然可以结合task多线程异步使用

关键特点:

​封装性:事件只能在声明类内部触发(Invoke),外部仅能订阅。
松耦合:发布者与订阅者无需直接依赖,提升代码可维护性

目的 一般类中一些变量改变之后需要外界感知响应的一些操作 wpf mvvm这种数据响应

标准.NET事件模式

EventHandler委托:内置泛型委托 统一的事件签名(object sender, EventArgs e)。就跟委托内置的func action一样就是为了让开发者方便使用 事件 而不必自己 必须声明一下 delegate 这代码 所以提前就封装好

​参数说明:
sender:触发事件的对象(事件源)。
e:事件参数,类型为 EventArgs(或其派生类)。若无额外数据需传递,使用 EventArgs.Empty
比如登录事件

public class UserAuthenticator
{// 定义事件public event EventHandler LoginSuccess;public void Login(string username, string password){// 模拟登录逻辑if (username == "admin" && password == "123456"){// 触发事件,传递当前对象和空参数LoginSuccess?.Invoke(this, EventArgs.Empty);}}
}// 使用
var authenticator = new UserAuthenticator();
authenticator.LoginSuccess += (sender, e) => 
{Console.WriteLine("登录成功!");
};
authenticator.Login("admin", "123456"); // 输出:登录成功!

自定义事件参数:通过继承EventArgs传递附加数据 就是规则 只要继承这个EventArgs 识别成事件 去使用
比如 声明事件 按钮点击事件ButtonClickEventArgs :EventArgs(使用EventHandler泛型委托)
public event EventHandler Click;

定义与使用 一样步骤 1 定义声明 2 触发实现方法 3 使用 也就是订阅
// 定义事件参数(继承EventArgs)
public class ButtonClickEventArgs : EventArgs {public string ButtonName { get; set; }
}// 发布者类
public class Button {// 声明事件(使用EventHandler泛型委托)public event EventHandler<ButtonClickEventArgs> Click;// 触发事件的方法protected virtual void OnClick(ButtonClickEventArgs e) {Click?.Invoke(this, e);  // 安全调用}public void SimulateClick() {OnClick(new ButtonClickEventArgs { ButtonName = "OK" });}
}// 订阅者类
public class Form {   private void OnButtonClicked(object sender, ButtonClickEventArgs e) {Console.WriteLine($"按钮 {e.ButtonName} 被点击!");}
}//在业务地方使用Button button = new Button();button.Click += OnButtonClicked;// 订阅事件button.SimulateClick();

这样在实现 Button的SimulateClick 方法时 事件触发 对于订阅者的 方法也将会执行

委托与事件的区别

在这里插入图片描述

使用场景

​1. 委托的典型应用
​回调机制:异步编程中的完成通知(如Task回调)。
​LINQ查询:动态定义查询条件(如Where方法的谓词参数)。
​策略模式:运行时切换算法逻辑(如排序策略):

Func<int[], int[]> strategy = SortAlgorithms.QuickSort;
int[] sorted = strategy(data);

​2. 事件的典型应用
​GUI交互:处理按钮点击、键盘输入等用户操作。
​异步通知:文件下载完成、网络请求响应等场景。
​观察者模式:实现松耦合的组件通信(如日志系统)

总结

​委托是方法引用的灵活容器,适用于回调、策略模式等场景。
​事件是委托的安全封装,专为松耦合的发布-订阅模型设计,广泛应用于GUI、异步编程

面试

1 谈谈你对委托的理解
答:委托 就是面向对象中 对方法的抽象封装 使得方法更加灵活的去使用 达到解耦扩展的目的

2 哪些使用场景
答: 最常见的异步回调 以及 linq 中查询表现形式 Lambda 表达式

3 事件是什么
答:事件 就是 private 安全把控的 委托 并搭配 发布订阅方式 异步方式 更进一步的解耦和扩展

4.事件哪些场景使用
答:最常见的就是 GUI 按钮点击事件这种 或者 文件下载异步完成通知事件 socket异步通信通知事件


http://www.ppmy.cn/news/1580113.html

相关文章

Vue:添加响应式数据

Vue&#xff1a;添加响应式数据 1. 什么是响应式&#xff1f; 修改 data 后&#xff0c;页面自动改变/刷新&#xff0c;这就是响应式。就像我们在使用 Excel 的时候&#xff0c;修改一个单元格中的数据&#xff0c;其它单元格的数据会联动更新&#xff0c;这也是响应式。在前…

基于FPGA轨道交通6U机箱CPCI脉冲板板卡

板卡简介&#xff1a; 本板为脉冲板&#xff0c;脉冲板主要执行CPU下达的指令&#xff0c;通过实现各种控制算法来调节PWM&#xff0c;然后输出光纤PWM信号来驱动变频器功率模块以达到控制电机的目的。 性能规格&#xff1a; 电源&#xff1a;DC5V&#xff1b;15V FPGA&…

主流区块链平台对 EVM 的依赖情况分类说明

文章目录 概要1. EVM 兼容链Binance Smart Chain (BSC)Polygon (PoS 链)Avalanche C-ChainFantomOptimism/Arbitrum 2. 非 EVM 链3. 混合型链AvalanchePolygon SupernetsBNB Chain 概要 1. EVM 兼容链 这些链直接支持以太坊虚拟机&#xff0c;开发者可用 Solidity 编写合约&a…

快速进行数据验证的优雅实现-注解

javax.validation包下的注解主要用于数据验证&#xff0c;确保数据符合特定的约束条件。以下是一个详细的表格&#xff0c;列出了这些注解的名称、作用、使用场景和示例&#xff1a; Excel 表格示例 注解名称作用使用场景示例AssertFalse确保字段值为 false布尔字段的验证Ass…

工程化与框架系列(32)--前端测试实践指南

前端测试实践指南 &#x1f9ea; 引言 前端测试是保证应用质量的重要环节。本文将深入探讨前端测试的各个方面&#xff0c;包括单元测试、集成测试、端到端测试等&#xff0c;并提供实用的测试工具和最佳实践。 测试概述 前端测试主要包括以下类型&#xff1a; 单元测试&a…

P11229 [CSP-J 2024] 小木棍

题目传送门 前言 我们班很多人这道题都爆了&#xff0c;原因是写分讨写挂了。不像睿智的我&#xff0c;直接暴力加上一点点思维。 解题思路 step 1 首先我们把每个数的贡献都列出来。 设 g ( x ) g(x) g(x) 为拼成 x x x 需要的木棍数量。 第一行表示数 x i x_i xi​…

正则表达式小结

正则表达式是一种用于描述文本模式的特殊字符串&#xff0c;它由一系列字符和特殊字符组成&#xff0c;用于匹配和操作文本数据。下面是正则表达式的一些常见规则&#xff1a; 字符匹配&#xff1a; 普通字符&#xff1a;正则表达式中的普通字符&#xff08;字母、数字、符号&a…

Flutter 学习之旅 之 flutter 使用 SQLite(sqflite) 实现简单的数据本地化 保存/获取/移除/判断是否存在 的简单封装

Flutter 学习之旅 之 flutter 使用 SQLite&#xff08;sqflite&#xff09; 实现简单的数据本地化 保存/获取/移除/判断是否存在 的简单封装 目录 Flutter 学习之旅 之 flutter 使用 SQLite&#xff08;sqflite&#xff09; 实现简单的数据本地化 保存/获取/移除/判断是否存在…