C#的委托原理刨析and事件原理刨析和两者的比较

news2024/11/16 7:55:56

什么是委托

委托是一种引用类型,表示对具有特定参数列表和返回类型的方法的引用。 在实例化委托时,你可以将其实例与任何具有兼容参数和返回类型的方法进行绑定。 你可以通过委托实例调用方法。

简单的理解,委托是方法的抽象类,它定义了方法的类型,可以实例化。和普通的类一样,可以申明变量进行赋值,可以当作参数传递,可以定义成属性。

委托具有以下属性:

  • 委托类似于 C++ 函数指针,但委托完全面向对象,不像 C++ 指针会记住函数,委托会同时封装对象实例和方法。

  • 委托允许将方法作为参数进行传递。

  • 委托可用于定义回调方法。

  • 委托可以链接在一起;具备单播、多播功能。

  • 方法不必与委托类型完全匹配。 有关详细信息,请参阅使用委托中的变体

  • 使用 Lambda 表达式可以更简练地编写内联代码块。 Lambda 表达式(在某些上下文中)可编译为委托类型。

1.委托基础介绍

1.1 delegate委托的声明

使用 delegate 关键字,定义具体的委托类型,Delegate至少0个参数,至多32个参数,可以无返回值,也可以指定返回值类型。

查看代码

方法绑定,进行调用

查看代码

1.2 Action 和 Func 背景

抽象的 Delegate 类提供用于松散耦合和调用的基础结构,但是这样看来,引发一个问题,无论何时需要不同的方法参数,这都会创建新的委托类型。 一段时间后此操作可能变得繁琐。 每个新功能都需要新的委托类型,幸运的是,没有必要这样做,框架已经帮我们定义Action 和 Func 类,我们可以直接申明进行使用

1.3 Action<T> 类

Action是无返回值的泛型委托。Action 委托的变体可包含多达 16 个参数,如 Action<T1,T2,T3,T4,T5,T6,T7,T8,T9,T10,T11,T12,T13,T14,T15,T16>。 重要的是这些定义对每个委托参数使用不同的泛型参数,这样可以具有最大的灵活性。框架源码,如图:

使用就很方便了,我们只需要直接申明委托类型进行使用,例:

查看代码

1.4 Func<T> 类

Func 委托的变体可包含多达 16 个输入参数,如 Func<T1,T2,T3,T4,T5,T6,T7,T8,T9,T10,T11,T12,T13,T14,T15,T16,TResult>。 按照约定,返回结果的类型始终是所有 Func 声明中最后一个参数的类型,利用out类型参数实现。

Func是有返回值的泛型委托,func至少0个参数,至多16个参数,根据返回值泛型返回。必须有返回值,不可void。框架源码,如下:

使用就很方便了,我们只需要直接申明委托类型进行使用,例:

查看代码

2. 委托实战案例

我这里就做一个多播案例,帮助大家理解,其实.NET core 日志框架和其他第三方日志框架,差不多就是这种套路

2.1 定义Logger类

这个类我们的定义好委托和调用委托的方法。

查看代码

2.2 定义文件记录器

一个写入文件的,文件记录器

查看代码

2.3 定义数据库记录器

一个写入不同数据库的,数据库记录器

查看代码

以上两个代码逻辑,博主就不介绍了,就用一个控制台输出,代表业务代码了

2.4 测试

测试一下,广播和委托删除效果

查看代码

运行效果:

在实际项目中,大家就自行发挥

3. 委托变量捕获

3.1效果演示

说到委托,博主也把这个重要的知识点讲解一下,这个知识点很多人可能不知道或者踩过坑,但掌握了这个知识点其实可以实现一些比较花哨功能。

这里博主就用一个案例进行体现变量捕获,这里代码博主就用 lambda 表达式 进行简写,不太熟悉的可以通过链接跳转进行学习。

逻辑就是,简单的累计一下数量,通过最终的值体现。这里博主分别申明两个整数型变量,通过两个委托分别累计,然后看各自的值。两个委托区别就是传值方式的不同。

查看代码

测试效果:

大家发现没?逻辑代码一下,只是参数传递方式不一样,结果截然不同:

委托1的方式:不改变变量的值,方法之间是不共享这个参数的。这种很容易理解,就和我们调用普通方法一样,变量是值类型,是拷贝了一个副本传给了方法进行使用

委托2的方式:改变变量的值,方法之间是共享这个参数的。这种就像引用类型参数一样,是不是很神奇,难道是利用了ref关键字实现的?

3.2原理刨析

其实没有大家想学的那么神秘,委托之所以使用方式和类无异,是因为它本身就是一个类,只是这个过程的定义由编译器帮我们做了,我们只需要使用C#的语法糖。接下来博主就带大家揭开委托的神秘面纱。

我也给大家画一个简单的编译=》执行的过程

3.2.1 委托真实面貌

博主就简单写了一个委托,然后通过IL DASM工具查看IL代码

查看代码

3.2.2模拟委托调用过程

查看代码

大家发现没,最终的IL代码一模一样。也就说,委托就是编译器帮我们把func编译成一个带invoke函数的func类和生成一个装捕获的变量和函数体的类,然后通过构造函数将对象引用和函数指针(获取指针就是大家所说的把非托管指针压入当前)传给func类的实例化。然后最终调用的时候,委托类的invoke函数会去调用真正的函数。就这样完成了对函数的抽象。

3.2.3 委托变量生命周期

现在大家是不是对委托有了一定的理解了,而委托涉及到的捕获变量和参数变量,生命周期就说得通了,也知道为啥委托改变了变量,能通知到原本的变量,因为对变量就行了类的装箱,打包成了一个一个引用类型,那方法外部当然知道变量的值被改变了,因为大家都是拿着引用对象的地址呀。下面做个生命周期小总结:

  • p变量是普通变量,当方法被销毁时,它就会被销毁。

  • count2变量是捕获变量,当委托实例被销毁时,它才会被销毁。

4. 事件

其实讲完委托,事件就很容易理解了, 博主就简单讲解一下,如果大家有需要,博主就再写一篇详细的讲解。

事件:实际上,事件是建立在对委托的语言支持之上的一种设计而已。

4.1 事件定义语法

/定义一个委托
4publicdelegatevoiddelegateRun();
5//定义一个事件6publicevent delegateRun eventRun;

简单的说,事件可以看作是一个委托类型的变量

4.2委托和事件共性:

它们都提供了一个后期绑定方案:在该方案中,组件通过调用仅在运行时识别的方法进行通信。 它们都支持单个和多个订阅服务器方法。 也就是单播和多播支持。 二者均支持用于添加和删除处理程序的类似语法。 最后,引发事件和调用委托使用完全相同的方法调用语法。 它们甚至都支持与 ?. 运算符一起使用的相同的 Invoke() 方法语法。

4.3 事件原理刨析

publicevent EventHandler<NewMailEventArgs> NewMail;  

可以看到当我们定义一个NewEvent时,编译器帮我们生成了:1. 一个private NewMail 字段,类型为 EventHandler<NewMailEventArgs>。 2.一个 add_NewMail 方法,用于将委托添加到委托链(内部调用了Delegate.Combine方法)。3.一个 remove_NewMail 方法,用于将委托从委托链移除(内部调用了Delegate.Remove方法)。对事件的操作,就是是对NewMail字段的操作。

4.4 如何选择

主要区别就是:

1.事件处理程序通过修改事件参数对象的属性将信息传回到事件源。 虽然这些惯用语可发挥作用,但它们不像从方法返回值那样自然。

2.包含事件的类以外的类只能添加和删除事件侦听器;只有包含事件的类才能调用事件。 事件通常是公共类成员。 相比之下,委托通常作为参数传递,并存储为私有类成员(如果它们全部存储)

3.当事件源将在很长一段时间内引发事件时,基于事件的设计会更加自然。比如基于事件的 UI 控件设计案例

总结:

(1)事件:事件时属于类的成员,所以要放在类的内部。

(2)委托:属于一个定义,是和类、接口类似的,通常放在外部。

所以事件这种架构设计思想还是很值得大家去学习的。

所以说,如果你的代码在不调用任何订阅服务器的情况下可完成其所有工作,使用基于事件的设计会更好点。

大家在项目中,怎么进行选择,就看实际需求了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/355045.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

L2-033 简单计算器

本题要求你为初学数据结构的小伙伴设计一款简单的利用堆栈执行的计算器。如上图所示&#xff0c;计算器由两个堆栈组成&#xff0c;一个堆栈 S1​ 存放数字&#xff0c;另一个堆栈 S2​ 存放运算符。计算器的最下方有一个等号键&#xff0c;每次按下这个键&#xff0c;计算器就…

RabbitMQ相关问题

文章目录避免重复消费(保证消息幂等性)消息积压上线更多的消费者&#xff0c;进行正常消费惰性队列消息缓存延时队列RabbitMQ如何保证消息的有序性&#xff1f;RabbitMQ消息的可靠性、延时队列如何实现数据库与缓存数据一致&#xff1f;开启消费者多线程消费避免重复消费(保证消…

字符集、ASCII、GBK、UTF-8、Unicode、乱码、字符编码、解码问题等

编码解码一、背景二、字符的相关概念三、字符集3.1 ASCII[ˈski]3.1.1 ASCII的编码方式3.1.2 EASCII3.2 GBK3.2.1 GB 2312-803.2.2 GBK的制订3.2.3 GBK的实现方式3.3 Unicode&#xff08;统一码、万国码&#xff09;3.3.1 Unicode的出现背景3.3.2 Unicode的编写方式3.3.3 Unico…

Verilog 学习第四节(从计数器到可控制线性序列机——LED实验进化六部曲)

从计数器到可控制线性序列机——LED实验进化六部曲一&#xff1a;让LED灯按照亮0.25s&#xff0c;灭0.75s的状态循环亮灭二&#xff1a;让LED灯按照亮0.25s&#xff0c;灭0.5s&#xff0c;亮0.75s&#xff0c;灭1s的状态循环亮灭三&#xff1a;让LED灯按照指定的亮灭模式亮灭&a…

Java程序员线上排查问题神器-Arthas

文章目录前言一、Arthas是什么&#xff1f;二、快速入门1.下载2.如何运行三、常用命令1.dashboard2.trace总结前言 最近公司项目版本迭代升级&#xff0c;在开发新需求导致没什么时间写博客。 在开发需求的过程中&#xff0c;我写了一个接口&#xff0c;去批量调内部已经写好…

浏览器工作原理详解

浏览器工作原理 以打开百度官网为例 在浏览器地址栏输入网址www.baidu.com &#xff0c;回车 这一过程发生了什么&#xff1f; 首先我们要知道www.baidu.com 这是个域名&#xff0c;需要通过DNS去解析为IP地址&#xff08;也就是服务器地址&#xff09;&#xff0c;然后返回…

yum/vim工具的使用

yum 我们生活在互联网发达的时代&#xff0c;手机电脑也成为了我们生活的必须品&#xff0c;在你的脑海中是否有着这样的记忆碎片&#xff0c;在一个明媚的早上你下定决心准备发奋学习&#xff0c;“卸载”了你手机上的所有娱乐软件&#xff0c;一心向学&#xff01;可是到了下…

前端面试题整理之HMTL篇(一)

HTML面试题&#xff08;一&#xff09; 前言&#xff1a; 面试题及答案解析&#xff0c;大部分来自网络整理&#xff0c;我自己做了一些简化&#xff0c;如果想了解的更多&#xff0c;可以搜索一下&#xff0c;前端面试题宝典微信公众号或者查百度&#xff0c;另外如果出现错误…

Windows环境下安装和配置Gradle

1. 概述 Gradle是Google公司基于JVM开发的一款项目构建工具&#xff0c;支持Maven&#xff0c;JCenter多种第三方仓库&#xff0c;支持传递性依赖管理&#xff0c;使用更加简洁和支持多种语言的build脚步文件&#xff0c;更多详情可以参阅Gradle官网 2. 下载 由于Gradle与S…

【opencv 系列】第2章 图片视频的读取和保存

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录前言1. 图片2.视频(读取摄像头、视频文件)2.1 从摄像头读视频2.2 从视频读取文件2.3 保存摄像头读取的视频2.4 图片的打开,读取和保存代码2.5 摄像头的打开,读取和保…

[LeetCode]1237. 找出给定方程的正整数解

题目链接&#xff1a;https://leetcode.cn/problems/find-positive-integer-solution-for-a-given-equation/description/ 题目描述&#xff1a; 样例1&#xff1a; 输入&#xff1a;function_id 1, z 5 输出&#xff1a;[[1,4],[2,3],[3,2],[4,1]] 解释&#xff1a;functi…

Linux 根目录与路径

目录 Linux的根目录 /usr目录&#xff08;存放用户数据&#xff09; /bin目录&#xff08;存放所有用户都可使用的应用程序&#xff09; /sbin目录&#xff08;存放管理员才可以使用的应用程序&#xff09; /opt目录&#xff08;存放第三方软件&#xff09; /boot目录&am…

[Android Studio] Android Studio Virtual Device(AVD)虚拟机的功能试用

&#x1f7e7;&#x1f7e8;&#x1f7e9;&#x1f7e6;&#x1f7ea; Android Debug&#x1f7e7;&#x1f7e8;&#x1f7e9;&#x1f7e6;&#x1f7ea; Topic 发布安卓学习过程中遇到问题解决过程&#xff0c;希望我的解决方案可以对小伙伴们有帮助。 &#x1f680;write…

Mindspore安装

本文用于记录搭建昇思MindSpore开发及使用环境的过程&#xff0c;并通过MindSpore的API快速实现了一个简单的深度学习模型。 什么是MindSpore? 昇思MindSpore是一个全场景深度学习框架&#xff0c;旨在实现易开发、高效执行、全场景覆盖三大目标。 安装步骤 鉴于笔者手头硬…

QM9数据集示例项目学习图神经网络

目录QM9数据集&#xff1a;QM9数据提取的特征&#xff1a;网络结构的设计官网示例代码注释&#xff1a;QM9数据集&#xff1a; QM9为小有机分子的相关的、一致的和详尽的化学空间提供量子化学特征&#xff0c;该数据库可用于现有方法的基准测试&#xff0c;新方法的开发&#…

【算法】两道算法题根据提供字母解决解码方法和城市的天际线天际线问题

算法目录解码方法Java解答参考&#xff1a;天际线问题Java解答参考&#xff1a;大家好&#xff0c;我是小冷。 上一篇了解了项目相关的知识点 接下来看下两道算法题吧&#xff0c;用Java解答&#xff0c;可能更能激发一下大脑思考。 解码方法 题目要求&#xff1a; 一条包含…

将 Supabase 作为下一个后端服务

对于想快速实现一个产品而言&#xff0c;如果使用传统开发&#xff0c;又要兼顾前端开发&#xff0c;同时又要花费时间构建后端服务。然而有这么一个平台&#xff08;Baas Backend as a service&#xff09;后端即服务&#xff0c;能够让开发人员可以专注于前端开发&#xff0c…

Java反序列化漏洞——CommonsCollections4.0版本—CC2、CC4

一、概述4.0版本的CommonsCollections对之前的版本做了一定的更改&#xff0c;那么之前的CC链反序列化再4版本中是否可用呢。实际上是可用的&#xff0c;比如CC6的链&#xff0c;引入的时候因为⽼的Gadget中依赖的包名都是org.apache.commons.collections &#xff0c;⽽新的包…

【构建工具】Gradle中文教程

文章目录Gradle 简介Gradle 概述基于声明的构建和基于约定的构建为以依赖为基础的编程方式提供语言支持构建结构化深度 APIGradle 的扩展多项目构建多种方式管理依赖Gradle 是第一个构建集成工具易于移植GroovyThe Gradle wrapper自由和开源为什么使用 Groovy?Gradle 安装先决…

Pycharm搭建一个Django项目

File->new project 点击create&#xff0c; 等待一下即可 查看安装 Django 版本&#xff1a; 在 Pycharm 底部选择 Terminal 然后在里面输入&#xff1a;python -m django --version 启动项目&#xff1a; 在 Terminal 里面输入: python manage.py runserver 查看文件目…