再谈C#中的委托和事件

0bc3b43d6e76a19d5bebf9807d6edfdf.gif

写在最前

我相信全网关于委托和事件的文章和概述,大家应该已经读过很多篇。但是就我的观察来看,大多数文在讲述这方面概念时,都会用烧开水和狗叫主人的例子来讲述事件怎么工作,这样比喻固然与生活联系紧密,但看多了难免有一些审美疲劳。

所以今天,我打算结合自己的一些工作经历,再来谈谈我个人对委托和事件的理解,希望能带给大家一些不一样的见解。

0a52ea973d168580cc3fbdbce8fcbf4d.gif

先谈概念

委托:一种引用类型,表示具有特定参数列表和返回类型的方法的引用。在实例化委托时,你可以将其实例与任何具有兼容签名和返回类型的方法相关联。你可以通过委托实例调用方法。委托用于将方法作为参数传递给其他方法。事件处理程序就是通过委托调用的方法。

事件:类或对象可以通过事件向其他类或对象通知发生的相关事情。发送(或引发)事件的类称为“发布者”,接收(或处理)事件的类称为“订阅者”。

以上概述来自MSDN官方文档:

https://docs.microsoft.com/zh-cn/dotnet/csharp/programming-guide/delegates/

从概念中我们其实已经可以看出,委托主要是对方法的一种引用,而事件则充当了多个类或者对象进行相互通知的桥梁。

如果我这么解释你可以明白的话,那么我们今天的主题就已经明朗了,下面我们就用具体的代码实例来讲述。

47da3a9f1d67ff0964859dc80b73e5d1.gif

再看例子

委托

我们需要先声明一个委托实例,在C#中,显示声明自定义委托采用delegate关键字,声明方式与声明普通方法相同,需要指定访问范围和返回类型,同时包含访问参数。

同时我们针对委托,声明对应的方法,方法的返回值和参数需要与委托保持一致,若不一致则会在委托传递方法时出现编译错误。

委托执行内部传递方法的方式是使用Invoke方法,此处需注意,C#中同时提供了BeginInvoke和EndInvoke的方法对,用于异步执行内部的方法,具体含义和用法可参考我之前的一篇文章:

浅谈.Net异步编程的前世今生----APM篇

下面我们一起来看一下示例:

using System;namespace DelegateAndEvent
{class Program{public delegate void DelegateWithNoParams();public delegate int DelegateSum(int a, int b);static void Main(string[] args){DelegateWithNoParams delegate1 = new DelegateWithNoParams(FunctionWithNoParams);delegate1.Invoke();DelegateSum delegate2 = new DelegateSum(FunctionSum);int c = delegate2.Invoke(10, 20);Console.WriteLine("带返回值和参数的方法,结果为:" + c);Console.Read();}public static void FunctionWithNoParams(){Console.WriteLine("无返回值无参数的方法");}public static int FunctionSum(int a, int b){int c = a + b;return c;}}
}

在此示例中,我们分别定义了一个无参数无返回值的委托和一个包含2个参数并返回int类型的委托,分别用于执行两种对应的方法。在两个委托执行对应的Invoke方法之后,会产生以下的结果:

ddc46bc1014796af12f9a1bf156032cf.png

结果和我们预期一致,程序同步顺序地执行了两个委托并打印出相应的结果。但是看到这里也许你会有一个疑问,既然委托执行时的结果与直接调用方法一致,那么我们为什么还需要使用委托来执行方法呢?

这时我们就要回到最初的定义:委托用于将方法作为参数传递给其他方法

由于实例化的委托是一个对象,因此可以作为参数传递或分配给一个属性。这允许方法接受委托作为参数并在稍后调用委托。这被称为异步回调,是在长进程完成时通知调用方的常用方法。当以这种方式使用委托时,使用委托的代码不需要知道要使用的实现方法。功能类似于封装接口提供的功能。

我们一起使用一个比较直观的例子来验证:

using System;namespace ConsoleApp1
{class Program{public delegate void Del(string message);static void Main(string[] args){Del handler = new Del(DelegateMethod);MethodWithCallback(1,2,handler);Console.Read();}public static void DelegateMethod(string message){Console.WriteLine(message);}public static void MethodWithCallback(int param1, int param2, Del callback){callback(string.Format("当前的值为:{0}", (param1 + param2)));}}
}

在这段代码中,我们声明了一个无返回值委托Del,用于接收传入的消息,并且该委托指向了一个调用控制台的方法DelegateMethod。而后续我们调用MethodWithCallback方法时,无需调用控制台相关方法,而是直接将Del委托的实例作为参数传入,就实现DelegateMethod方法的调用。这个实例就是我们上述提到的异步回调和委托对方法的引用。

运行结果如下:

0f9abf47ad6eee597a38345137c92b82.png

此处我使用了JetBrains出品的IDE Rider,因此截图界面会与VS有所不同,喜欢轻量级IDE的同学可以试试这款,有30天的免费试用期,地址:Rider: The Cross-Platform .NET IDE from JetBrains,此处不再过多讲解。

根据我们上述的实例讲解,大家对委托的作用及使用场景应该有了初步的理解,但是我们仔细想一想,上述的场景似乎少了些什么,为什么我们的委托始终只能指向一个方法进行调用呢?

这里就要引出我们接下来的概念:多播委托。

事实上,委托是可以调用多个方法的,这种方式就叫做多播委托,在C#中,我们可以使用+=的运算符,将其他委托附加到当前委托之后,就可以实现多播委托,相关示例如下:

using System;namespace ConsoleApp1
{class Program{public delegate void Del();static void Main(string[] args){Del handler = new Del(DelegateMethod1);Del handlerNew = new Del(DelegateMethod2);handler += handlerNew;handler.Invoke();Console.Read();}public static void DelegateMethod1(){Console.WriteLine("天才第一步!");}public static void DelegateMethod2(){Console.WriteLine("天才第二步!");}}
}

在这个示例中,我们重新编写了一个方法叫DelegateMethod2,同时我们又声明了一个新的委托对象handlerNew指向该方法。接着我们使用+=的方式将handlerNew添加至handler并执行该委托,得到的结果如下:

e92fc8019a2cb932428de57374f48007.png

如我们先前所料,多播委托把多个方法依次进行了执行。此时如果某个方法发生异常,则不会调用列表中的后续方法,如果委托具有返回值和/或输出参数,它将返回上次调用方法的返回值和参数。与增加方法相对应,若要删除调用列表的方法,则可以使用-=运算符进行操作。

关于委托的理解与常用方式,我们就讲解到这里,事实上,多播委托常用于事件处理中,由此可见,事件与委托有着千丝万缕的联系,下面我们就拉开事件的序幕。

事件

如前文讲解时所说,事件是一种通知行为,因此要分为事件发布者和事件订阅者。而且在.Net中,事件基于EventHandler委托和EventArgs基类的,因此我们在声明事件时,需要先定义一个委托类型,然后使用event关键字进行事件的定义。

相关的示例如下:

using System;namespace ConsoleApp1
{public class PublishEvent{public delegate void NoticeHandler(string message);public event NoticeHandler NoticeEvent;public void Works(){//触发事件OnNoticed();}protected virtual void OnNoticed(){if (NoticeEvent != null){//传递事件及参数NoticeEvent("Notice发布的报警信息!");}}}public class SubscribEvent{public SubscribEvent(PublishEvent pub){//订阅事件pub.NoticeEvent += PrintResult;}/// <summary>/// 订阅事件后的响应函数/// </summary>/// <param name="message"></param>void PrintResult(string message){Console.WriteLine(string.Format("已收到{0}!采取措施!",message));}}class Program{static void Main(string[] args){PublishEvent publish = new PublishEvent();SubscribEvent subscrib = new SubscribEvent(publish);//触发事件publish.Works();Console.Read();}}
}

从事例中我们可以看出,我们分别定义了发布者和订阅者相关的类。

在发布者中,我们需要声明一个委托NoticeHandler,然后定义一个此类型的事件NoticeEvent。在定义对象之后,我们需要对事件进行执行,因此有了OnNoticed方法,此方法只用于事件本身的执行。那么什么时候才能执行事件呢?于是我们又有了触发该事件的方法Works,当Works方法被调用时,就会触发NoticeEvent事件。

而在订阅者中,我们需要对NoticeEvent事件进行订阅,因此我们需要发布者的对象PublishEvent,同时需要对它的事件进行订阅。正如我们前文所说,订阅使用+=的方式,与多播委托的使用是一致的,而+=后的对象正是我们需要响应后续处理的方法PrintResult。当事件被触发时,订阅者会接收到该事件,并自动执行响应函数PrintResult。

执行结果如下图所示:

9255d188c64ab21eb897735ecc681e7d.png

从执行结果中我们可以看出,在事件被触发后,订阅者成功接收到了发布者发布的事件内容,并进行自动响应,而我们在此过程中从未显式调用订阅者的任何方法,这也是事件模型的本质意义:从发布到订阅。

在微软官方文档中提到,事件是一种特殊的多播委托,只能从声明它的类中进行调用。客户端代码通过提供对应在引发事件时调用的方法的引用来订阅事件。这些方法通过事件访问器添加到委托的调用列表中,这也是我们可以使用+=去订阅事件的原因所在,而取消事件则和多播委托一致,使用-=的方式。

关于事件的使用场景还有一种与多线程通知相关的典型用法,具体含义和用法可参考我之前的另一篇文章:

浅谈.Net异步编程的前世今生----EAP篇

93e97ea3dba81576d59b1d27055c1f69.gif

最后总结

本文我们讲解了委托和事件之间的关系,以及它们的使用场景。在我个人的工作经历中,曾经有3年左右的时间从事C/S相关的开发工作,其中包含了大量多线程、委托和事件的使用场景。主要用于在开发WinForm程序时,不同窗体(包含父子窗体)之间进行相互通信,其中都是基于事件的发布和订阅作为实现的。而委托的使用场景则更多,很多C#方法在使用时都会传入一个Action或者Func委托作为参数,而这些参数同时又支持Lambda表达式,这就又引出了匿名函数的概念,由于篇幅所限,此处不再进行讲解,大家可以自行搜索资料进行了解和学习。

除了具体的技术点之外,在我们的设计模式中也有事件的使用身影,最典型的莫过于观察者模式。关于观察者模式,网上众说纷纭,也有很多资料会将它与发布-订阅模式混为一谈。而实际上这两种模式并不完全是同一种概念和实现方式,那么下一次我们将会从设计模式着手,谈一谈观察者模式和发布-订阅模式的异同,敬请期待!

9f7f7b3308efb59e67570d6128a6dfc0.png

您的点赞和在看是我创作的最大动力,感谢支持

c3670454da53f887ff2ddd142a92da62.png

48e5408b8f7e6ab4752d606c1a70c162.png

e9f43a13579630a719bbd349a93c242c.png

公众号:wacky的碎碎念

知乎:wacky

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

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

相关文章

js.domReady

2019独角兽企业重金招聘Python工程师标准>>> var dom [], dom_isReady false, domReady function (a) { if (dom_isReady) a(); else dom.push(a) }, dom_fireReady function () { if (!dom_isReady) { if (!document.body) return setTimeout(dom_fireReady, 1…

Android之监测database的改变--notifyChange

我们在ContentProvider的insert,update,delete等改变之后调用getContext().getContentResolver().notifyChange(uri, null);这样就通知那些监测databases变化的observer了&#xff0c;而你的observer可以在一个service里面注册。 以Downloadmanger为例子&#xff1a; 定义Cont…

双一流高校出新规:研究生未经导师同意发论文,不得用于毕业、评奖!

全世界只有3.14 % 的人关注了爆炸吧知识本文转自&#xff1a;募格学术近日&#xff0c;吉林大学研究生院发布文件《吉林大学关于加强对研究生在学期间公开发表论文等学术成果管理的通知》&#xff0c;因内容涉及校内研究生论文发表要求&#xff0c;引起很多研究生的注意。其中包…

android http 三次 握手,面试解析:3次握手与4次挥手

在面试中&#xff0c;三次握手和四次挥手可以说是问的最频繁的一个知识点了&#xff0c;我相信大家也都看过很多关于三次握手与四次挥手的文章。今天的这篇文章&#xff0c;重点是围绕着面试&#xff0c;我们应该掌握哪些比较重要的点&#xff0c;哪些是比较多被面试官给问到的…

微服务组件记事本:Skywalking执行效果 · 多图篇

立冬时节知多少今天立冬&#xff0c;周末两天在家继续研究了下Skywalking&#xff0c;感觉这个组件还是很不错的&#xff0c;无论是设计思想还是架构设计&#xff0c;都能从中受到启发和帮助&#xff0c;建议感兴趣的小伙伴可以看看&#xff0c;当然&#xff0c;如果不感兴趣还…

堆和栈的区别 (转贴)

从其他博客复制过来的&#xff0c;因为这个够详细&#xff0c;转过来学习一下&#xff01; 堆和栈的区别一、预备知识—程序的内存分配一个由c/C编译的程序占用的内存分为以下几个部分1、栈区&#xff08;stack&#xff09;— 由编译器自动分配释放 &#xff0c;存放函数的参数…

ios和android交互差异,Android 和 iOS 主要交互区别整理

我简单整理了一下Android 和 iOS 主要的交互区别&#xff0c;如果有遗漏欢迎批评补充。我总结有下面五类区别&#xff1a;一、界面布局形式的差异1 iOS 的 Tab Bar在iOS应用内最多用Tab Bar来切换不同的模块&#xff0c;Tab Bar 也是最受欢迎最容易被用户认知的方式。2 Android…

要比惨吗?看看这个女人

全世界只有3.14 % 的人关注了爆炸吧知识她从不知道辞职是什么滋味&#xff0c;因为被炒的总是她&#xff1b;连续被三家公司炒掉之后&#xff0c;她不得不自己做SOHO。生了双胞胎&#xff0c;本是喜事&#xff1b;但还在月子里&#xff0c;却发现老公出轨。小三还没打走&#x…

【Blog.Core开源】完成升级.NET 6.0

(千呼万唤始出来&#xff0c;_ _ _ _ _ _ _)是不是每个人都已经尝试一遍vs2022和.NET6.0了&#x1f601;&#xff0c;从各个微信群和盆友圈就可见一斑。今天一大早&#xff0c;一位粉丝就发私信&#xff0c;让升级6.0&#xff0c;这么简单的需求&#xff0c;必须满足&#xff0…

Jenkins file一行代码部署.NET程序到K8S

Jenkins file一行代码部署.NET程序什么是Jenkins共享库随着微服务的增多&#xff0c;每个项目的都需要pipline文件&#xff0c;这样的话Pipeline代码冗余度高&#xff0c;并且pipeline的功能越来越复杂。jenkins可以使用Shared Lib&#xff0c;将一些公共的pipeline抽象做成模块…

吵架后女生和男生的夜晚!所有男生都这样吗?

1 猫&#xff1a;哪个瓜娃子开的灯&#xff1f;&#xff01;2 神回复在哪里3 舔舔舔舔舔&#xff0c;被发现了&#xff01;4 喵喵&#xff1a;你当我傻吗&#xff1f;5 今晚可不可以到你家吃饭6 吵架后女生的夜晚vs吵架后男生的夜晚太真实了7 打游戏时候 对方队友和我方…

项目背景介绍

Adventure 项目&#xff08;1&#xff09; 墨翟坐在办公室里&#xff0c;正和秘书 Alan 开会&#xff0c;讨论着一件让他和 Alan 都很烦心的事情。Adventure 在全球都有工厂&#xff0c;制造各种不同的产品。作为制造部门的负责人&#xff0c;墨翟需要每个月了解各个工厂的运行…

不想升Win11?Win10新版马上到!

微软正式宣布 Windows 10 操作系统的下一个功能更新&#xff1a;Windows 10 版本 21H2。微软表示&#xff1a;虽然我们对下一代 Windows 11 感到兴奋&#xff0c;但也专注于支持 Windows 10 上超过 13 亿台活跃设备。当人们继续在混合和远程环境中工作、学习和娱乐时&#xff0…

娱乐项目和女朋友哪个重要?

1 这招风卷残云我只演示一遍&#xff0c;看好了2 摄影师给男生拍照VS给女生拍照3 这个视力表是永远不会近视的4 娱乐项目和女朋友哪个重要&#xff1f;小伙子你飘了啊5 最怕空气突然安静6 “灵魂六问”7 原来是这样周末愉快↓ ↓ ↓

这些高校竟因名字太“坑爹”被误会为三本?盘点九所实力强劲但被名字耽误的大学...

全世界只有3.14 % 的人关注了爆炸吧知识有这样一个段子&#xff1a;西北某地一学生&#xff0c;志存高远&#xff0c;矢志从医。首次高考考入泸州医学院&#xff0c;到校后发现不理想&#xff0c;果断返乡复读。一年后成功考入四川医科大学&#xff0c;到校后傻眼了&#xff0c…

【全】.net core平台单元/集成测试结果、覆盖率、圈复杂度到可视化HTML报告之路...

.net core 平台的测试框架有好几个可供选择&#xff0c;内置的MSTest框架、NUnit框架以及强大好用的xUnit框架&#xff0c;依托宇宙最强的编辑器Visual Studio&#xff0c;可以开展非常友好的测试以及快乐的展示测试的结果。如果仅限于此&#xff0c;那么这篇文章对你来说就是多…

哪种HTML列表会自动编号,HTML列表的种类

HTML 支持有序、无序和定义列表无序列表无序列表是一个项目的列表&#xff0c;此列项目使用粗体圆点(典型的小黑圆圈)进行标记。无序列表始于 标签。每个列表项始于 。(推荐学习&#xff1a;HTML入门教程)CoffeeMilk浏览器显示如下&#xff1a;CoffeeMilk列表项内部可以使用段落…

Android之AsyncTask异步任务详解总结

Android 多线程----AsyncTask异步任务详解 【正文】 本文将讲解一下Android的多线程的知识&#xff0c;以及如何通过AsyncTask机制来实现线程之间的通信。 一、Android当中的多线程&#xff1a; 在Android当中&#xff0c;当一个应用程序的组件启动的时候&#xff0c;并且没有其…

docker源码_使用docker、Jenkins、gitlee、springboot、搭建个人博客网站 并实现CI/CD 外加机器人提醒...

看朱成碧思纷纷&#xff0c;憔悴支离为忆君。--- barcke前言:服务器用的是云服务器买的阿里的linux、jdk1.8、docker服务端使用的为阿里的docker镜像仓库、mysql使用5.7版本的、redis使用最新版即可、文件服务使用的阿里的oss.项目采用jib推送docker镜像.附录: git源码地址—-h…

初次见面,如何令人永远难忘

1 初次见面&#xff0c;如何令人永远难忘2 见到熟人和见到偶像的区别3 真正的好朋友是在最紧急的时刻帮你一把的那个人4 当你正在减肥时&#xff0c;你朋友约你出去吃饭5 被这样的友谊感动到了6 等我老了也要和大爷一样做一个时尚的人不说别的大爷好酷↓ ↓ ↓