当前位置: 首页 > news >正文

C# 一文读懂委托与事件

什么是委托 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异步通信通知事件

相关文章:

  • Web Component 教程(二):如何有效管理和使用自定义属性
  • ✎ 一次有趣的经历
  • Cross-Silo Prototypical Calibration for Federated Learning with Non-IID Data
  • 【操作系统安全】任务6:Linux 系统文件与文件系统安全 学习指南
  • 【项目合集】基于ESP32的智能化妆柜
  • Linux进程信号(上)
  • Python第五章03:函数返回值和None类型
  • 网络编程知识预备阶段
  • 东隆科技携手PRIMES成立中国校准实验室,开启激光诊断高精度新时代
  • 【免费】2004-2017年各地级市实际利用外资数据
  • Grokking System Design 系统设计面试问题
  • 从零开始实现一个HTML5飞机大战游戏
  • java 中散列表(Hash Table)和散列集(Hash Set)是基于哈希算法实现的两种不同的数据结构
  • 【渗透测试】webpack对于渗透测试的意义
  • Linux 如何上传本地文件以及下载文件到本地命令总结
  • WSL2配置Humanoidbench问题mujoco.FatalError: OpenGL version 1.5 or higher required
  • Bash中关于制表符\t站位情况说明
  • 滑动窗口算法详解:从入门到精通
  • 44运营干货:提高用户留存和粘性方式汇总
  • 传输层协议 ——— TCP协议
  • 国新办将于5月8日10时就《民营经济促进法》有关情况举行新闻发布会
  • A股三大股指集体高开大涨超1%,券商、房地产涨幅居前
  • 中国驻俄大使张汉晖人民日报撰文:共襄和平伟业,续谱友谊新篇
  • 山东滕州市醉驾交通事故肇事人员已被刑拘
  • 中年人多活动有助预防阿尔茨海默病
  • 2025五一档电影票房破7亿