C# 从1到Core--委托与事件

委托与事件在C#1.0的时候就有了,随着C#版本的不断更新,有些写法和功能也在不断改变。本文温故一下这些改变,以及在NET Core中关于事件的一点改变。

一、C#1.0 从委托开始

1. 基本方式

  什么是委托,就不说概念了,用例子说话。

  某HR说他需要招聘一个6年 .NET5 研发经验的“高级”工程师,他想找人(委托)别人把这条招聘消息发出去。这样的HR很多,所以大家定义了一个通用的发消息规则:

public delegate string SendDelegate(string message);

  这就像一个接口的方法,没有实际的实现代码,只是定义了这个方法有一个string的参数和返回值。所有想发招聘消息的HR只要遵守这样的规则即可。

委托本质上是一个类,所以它可以被定义在其他类的内部或外部,根据实际引用关系考虑即可。本例单独定义在外部。

为HR定义了一个名为HR的类:

public class HR
{public SendDelegate sendDelegate;public void SendMessage(string msg){sendDelegate(msg);}
}

  HR有一个SendDelegate类型的成员,当它需要发送消息(SendMessage)的时候,只需要调用这个sendDelegate方法即可。而不需要实现这个方法,也不需要关心这个方法是怎么实现的。

当知道这个HR需要发送消息的时候,猎头张三接了这个帮忙招人的工作。猎头的类为Sender,他有一个用于发送消息的方法Send,该方法恰好符合众人定义的名为SendDelegate的发消息规则。这有点像实现了一个接口方法,但这里不要求方法名一致,只是要求方法的签名一致。

public class Sender
{public Sender(string name){this.senderName = name;}private readonly string senderName;public string Send(string message){string serialNumber = Guid.NewGuid().ToString();Console.WriteLine(senderName + " sending....");Thread.Sleep(2000);Console.WriteLine("Sender: " + senderName + " , Content: " + message + ", Serial Number: "  + serialNumber);return serialNumber;}
}

猎头帮助HR招人的逻辑如下:

public void Test()
{//一个HRHR hr = new HR();//猎头张三来监听,听到HR发什么消息后立刻传播出去Sender senderZS = new Sender("张三");hr.sendDelegate = senderZS.Send;    //HR递交消息hr.SendMessage("Hello World");
}

猎头将自己的发消息方法“赋值”给了HR的SendDelegate方法,为什么可以“赋值”? 因为二者都遵守SendDelegate规则。 就像A和B两个变量都是int类型的时候,A可以赋值给B一样。

这就是一个简单的委托过程,HR将招人的工作委托给了猎头,自己不用去做招人的工作。

但经常一个招聘工作经常会有多个猎头接单,那就有了多播委托。

2. 多播委托

 看一下下面的代码:

public void Test()
{//一个HRHR hr = new HR();//猎头张三来监听,听到HR发什么消息后立刻传播出去Sender senderZS = new Sender("张三");hr.sendDelegate = senderZS.Send;//快嘴李四也来了Sender senderLS = new Sender("李四");hr.sendDelegate += senderLS.Send;//HR递交消息hr.SendMessage("Hello World");
}

与之前的代码改变不大, 只是添加了李四的方法绑定,这样HR发消息的时候,张三和李四都会发出招人的消息。

这里要注意李四绑定方法的时候,用的是+=而不是=,就像拼接字符串一样,是拼接而不是赋值,否则会覆盖掉之前张三的方法绑定。

对于第一个绑定的张三,可以用=号也可以用+=(记得之前好像第一个必须用=,实验了一下现在二者皆可)。

这同时也暴露了一些问题:

  • 如果后面的猎头接单的时候不小心(故意)用了=号, 那么最终前面的人的绑定都没有了,那么他将独占这个HR客户,HR发出的消息只有他能收到。

  • 可以偷偷的调用猎头的hr.sendDelegate

public void Test()
{//一个HRHR hr = new HR();//大嘴张三来监听,听到HR发什么消息后立刻传播出去Sender senderZS = new Sender("张三");//hr.sendDelegate -= senderZS.Send; //即使未进行过+=  直接调用-=,也不会报错hr.sendDelegate += senderZS.Send;//快嘴李四也来了Sender senderLS = new Sender("李四");hr.sendDelegate += senderLS.Send;//移除//hr.sendDelegate -= senderZS.Send;//风险:注意上面用的符号是+=和-=   如果使用=,则是赋值操作,//例如下面的语句会覆盖掉之前所有的绑定//hr.sendDelegate = senderWW.Send;//HR递交消息hr.SendMessage("Hello World");//风险:可以偷偷的以HR的名义偷偷的发了一条消息    sendDelegate应该只能由HR调用   hr.sendDelegate("偷偷的发一条");}

3. 通过方法避免风险

  很自然想到采用类似Get和Set的方式避免上面的问题。既然委托可以像变量一样赋值,那么也可以通过参数来传值,将一个方法作为参数传递。

public class HRWithAddRemove{private SendDelegate sendDelegate;public void AddDelegate(SendDelegate sendDelegate){this.sendDelegate += sendDelegate; //如果需要限制最多绑定一个,此处可以用=号}public void RomoveDelegate(SendDelegate sendDelegate){this.sendDelegate -= sendDelegate;}public void SendMessage(string msg){sendDelegate(msg);}}

经过改造后的HR,SendDelegate方法被设置为了private,之后只能通过Add和Remove的方法进行方法绑定。

4.模拟多播委托机制

通过上面委托的表现来看,委托就像是保存了一个相同方法名的集合 List<SendDelegate> ,可以向集合中添加或移除方法,当调用这个委托的时候,会逐一调用该集合中的各个方法。

例如下面的代码( 注意这里假设SendDelegate只对应一个方法 ):

public class HR1
{public void Delegate(SendDelegate sendDelegate){sendDelegateList = new List<SendDelegate> { sendDelegate }; //对应=}public void AddDelegate(SendDelegate sendDelegate){sendDelegateList.Add(sendDelegate); //对应+=}public void RomoveDelegate(SendDelegate sendDelegate){sendDelegateList.Remove(sendDelegate);//对应-=}public List<SendDelegate> sendDelegateList;public void SendMessage(string msg){foreach (var item in sendDelegateList){item(msg);}}
}

二、C#1.0 引入事件

  1.简单事件

  如果既想使用-=和+=的方便,又想避免相关功能开闭的风险怎么办呢?可以使用事件:

public class HRWithEvent{public event SendDelegate sendDelegate;public void SendMessage(string msg){sendDelegate(msg);}}

 只是将SendDelegate前面添加了一个event标识,虽然它被设置为public,但如下代码却会给出错误提示: 事件“HRWithEvent.sendDelegate”只能出现在 += 或 -= 的左边(从类型“HRWithEvent”中使用时除外) 

 hr.sendDelegate = senderZS.Send;hr.sendDelegate("偷偷的发一条");

  2.事件的访问器模式

   上文为委托定义了Add和Remove方法,而事件支持这样的访问器模式,例如如下代码:

public class CustomerWithEventAddRemove{private event SendDelegate sendDelegate;public event SendDelegate SendDelegate{add { sendDelegate += value; }remove { sendDelegate -= value; }}public void SendMessage(string msg){sendDelegate(msg);}}

 可以像使用Get和Set方法一样,对事件的绑定与移除进行条件约束。 

  3. 控制绑定事件的执行

  当多个委托被绑定到事件之后,如果想精确控制各个委托的运行怎么办,比如返回值(虽然经常为void)、异常处理等。

第一章第4节通过一个List<SendDelegate> 模拟了多播委托的绑定。 会想到如果真能循环调用一个个已绑定的委托,就可以精确的进行控制了。那么这里说一下这样的方法:

public class HRWithEvent{public event SendDelegate sendDelegate;public void SendMessage(string msg){//sendDelegate(msg);  此处不再一次性调用所有if (sendDelegate != null){Delegate[] delegates = sendDelegate.GetInvocationList(); //获取所有已绑定的委托foreach (var item in delegates){((SendDelegate)item).Invoke(msg); //逐一调用}}}}

 这里通过Invoke方法逐一调用各个Delegate,从而实现对每一个Delegate的调用的控制。若需要异步调用,则可以通过BeginInvoke方法实现(.NET Core之后不再支持此方法,后面会介绍。)

((SendDelegate)item).BeginInvoke(msg,null,null);

  4. 标准的事件写法

  .NET 事件委托的标准签名是:

void OnEventRaised(object sender, EventArgs args);

 

  返回类型为 void。 事件基于委托,而且是多播委托。 参数列表包含两种参数:发件人和事件参数。 sender 的编译时类型为 System.Object

  第二种参数通常是派生自 System.EventArgs 的类型(.NET Core 中已不强制要求继承自System.EventArgs,后面会说到)

  将上面的例子修改一下,改成标准写法,大概是下面代码的样子:

public class HRWithEventStandard
{public delegate void SendEventHandler(object sender, SendMsgArgs e);public event SendEventHandler Send;public void SendMessage(string msg){var arg = new SendMsgArgs(msg);Send(this,arg); //arg.CancelRequested 为最后一个的值   因为覆盖}
}public class SendMsgArgs : EventArgs
{public readonly string Msg = string.Empty;public bool CancelRequested { get; set; }public SendMsgArgs(string msg){this.Msg = msg;}
}

三、随着C#版本改变

1. C#2.0 泛型委托

  C#2.0 的时候,随着泛型出现,支持了泛型委托,例如,在委托的签名中可以使用泛型,例如下面代码

public delegate string SendDelegate<T>(T message);

这样的委托适用于不同的参数类型,例如如下代码(注意使用的时候要对应具体的类型)

public delegate string SendDelegate<T>(T message);public class HR1
{public SendDelegate<string> sendDelegate1;public SendDelegate<int> sendDelegate2;public SendDelegate<DateTime> sendDelegate3;
}public static class Sender1
{public static string Send1(string msg){return "";}public static string Send2(int msg){return "";}
}public class Test
{public void TestDemo(){HR1 hr1 = new HR1();hr1.sendDelegate1 = Sender1.Send1; // 注意使用的时候要对应具体的类型hr1.sendDelegate2 = new SendDelegate<int>(Sender1.Send2);hr1.sendDelegate3 = delegate (DateTime dateTime) { return dateTime.ToLongDateString(); };}
}

2. C#2.0 delegate运算符

delegate 运算符创建一个可以转换为委托类型的匿名方法:

例如上例中这样的代码:

hr1.sendDelegate3 = delegate (DateTime dateTime) { return dateTime.ToLongDateString(); };

3. C#3.0 Lambda 表达式

从 C# 3 开始,lambda 表达式提供了一种更简洁和富有表现力的方式来创建匿名函数。 使用 => 运算符构造 lambda 表达式,

例如“delegate运算符”的例子可以简化为如下代码:

hr1.sendDelegate3 = (dateTime) => { return dateTime.ToLongDateString(); };

 

4.C#3,NET Framework3.5,Action 、Func、Predicate

Action 、Func、Predicate本质上是框架为我们预定义的委托,在上面的例子中,我们使用委托的时候,首先要定义一个委托类型,然后在实际使用的地方使用,而使用委托只要求方法名相同,在泛型委托出现之后,“定义委托”这一操作就显得越来越累赘,为此,系统为我们预定义了一系列的委托,我们只要使用即可。

例如Action的代码如下:

实际上定义了最多16个参数的无返回值的委托。

Func与此类似,是最多16个参数的有返回值的委托。Predicate则是固定一个参数以及bool类型返回值的委托。

public delegate bool Predicate<T>(T obj);

 5. .NET Core 异步调用

第2.3节中,提示如下代码在.NET Core中已不支持

((SendDelegate)item).BeginInvoke(msg,null,null);

 

会抛出异常:

System.PlatformNotSupportedException:“Operation is not supported on this platform.”

 

需要异步调用的时候可以采用如下写法:

Task task = Task.Run(() => ((SendDelegate)item).Invoke(msg));

 

对应的 EndInvoke() 则改为: task.Wait(); 

 

 5. .NET Core的 EventHandler<TEventArgs>

.NET Core 版本中,EventHandler<TEventArgs> 定义不再要求 TEventArgs 必须是派生自 System.EventArgs 的类, 使我们使用起来更为灵活。

例如我们可以有这样的写法:

EventHandler<string> SendNew

 这在以前的版本中是不允许的。

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

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

相关文章

用一个单链表L实现一个队列(算法导论第十章10.2-3)

用一个单链表L实现一个队列&#xff08;算法导论第十章10.2-3) template<typename T> class HalfNode { public:T key;HalfNode* next; public:HalfNode(){next nullptr;};HalfNode(const T key):key(key){next nullptr;}; }; template<typename T> class Singl…

开源导入导出库Magicodes.IE 多sheet导入教程

多Sheet导入教程说明本教程主要说明如何使用Magicodes.IE.Excel完成多个Sheet数据的Excel导入。要点多个相同格式的Sheet数据导入多个不同格式的Sheet数据导入主要步骤1. 多个相同格式的Sheet数据导入1.1 创建导入Sheet的Dto主要代码如下所示&#xff1a;学生数据Dto/// <su…

Java实现二叉树

二叉树: package Tree;import java.util.ArrayList; import java.util.LinkedList; import java.util.Queue; import java.util.Stack;public class BinTree {private class TreeNode{int data;TreeNode left;TreeNode right;public TreeNode(){data 0;left null;right nul…

解决 Azure AD 在 Azure Front Door 下登录失败的问题

点击上方关注“汪宇杰博客” ^_^导语最近我给博客系统加上了 Azure Front Door&#xff0c;集齐了12项 Azure 服务打算召唤神龙。没想到刚上线&#xff0c;Azure AD 的单点登录就爆了。OIDC 跳转错误当我尝试登录博客后台的时候&#xff0c;OIDC的跳转URL突然变成了 https://ed…

二叉搜索树的插入与删除(C语言)

代码如下: BinTree Insert( BinTree BST, ElementType X ) {if( !BST ){ /* 若原树为空&#xff0c;生成并返回一个结点的二叉搜索树 */BST (BinTree)malloc(sizeof(struct TNode));BST->Data X;BST->Left BST->Right NULL;}else { /* 开始找要插入元素的位置 */…

算法导论10-2.4题

算法导论10-2.4题 template<typename T> typename List<T>::Node* List<T>::search01(const T &key) {Nil->key key;Node * current Nil->next;while (current->key!key){current current->next;}return current; }其他全部代码链接

Linux中作业控制命令

开门见山&#xff0c;最近.NET劝退师要在linux上写些长时间运行的脚本&#xff0c;获取Azure BlobStorage存储的数据。记录一下Linux中后台执行作业的命令。Linux作业作业(Job)是shell管理的进程(每个job都有一个关联的PID)&#xff0c;每个作业会被分配一个线性job ID。有两种…

使用单项循环链表实现字典操作(算法导论第十章10.2-5题)

使用单项循环链表实现字典操作(算法导论第十章10.2-5题) template<typename T> void insert(SingleCycleL<T> & l,T key) {HalfNode<T> * t new HalfNode<T>(key);t->next l.Nil->next;l.Nil->next t; } template<typename T> …

《Unit Testing》2.1 经典学派如何做测试隔离

经典学派如何解决隔离问题首先&#xff0c;再回顾一下单元测试的三个重要特性&#xff1a;验证一小段代码&#xff08;或者叫一个单元&#xff09;执行速度快使用隔离的方式进行针对第一个特性就会引出一个问题&#xff1a;多小的一段代码才足够小&#xff1f;如果你采用针对每…

AVL树的旋转与插入(C语言)

代码如下: typedef struct AVLNode *Position; typedef Position AVLTree; /* AVL树类型 */ struct AVLNode{ElementType Data; /* 结点数据 */AVLTree Left; /* 指向左子树 */AVLTree Right; /* 指向右子树 */int Height; /* 树高 */ };int Max ( int a, int b …

单项循环链表

单项循环链表 // // Created by 许加权 on 2021/7/12. //#ifndef C11LEARN_SINGLECYCLELINK_H #define C11LEARN_SINGLECYCLELINK_H #include "chapter10.h" template<typename T> class SingleCycleL { public:HalfNode<T> *Nil; public:SingleCycleL(…

Hacker News热文:请停止学习框架,学习领域驱动设计(DDD)(获500个点赞)

在 Hacker News 上获得接近 500 个点赞的一篇名为《停止学习框架》的文章称&#xff1a;我们是程序员&#xff0c;每天都在了解最新的技术&#xff0c;每天都在学习编程语言、框架和库&#xff0c;因为我们知道的现代编程工具越多越好&#xff0c;对吧&#xff1f;不停地追随 A…

堆的定义与操作(C语言)

代码如下: typedef struct HNode *Heap; /* 堆的类型定义 */ struct HNode {ElementType *Data; /* 存储元素的数组 */int Size; /* 堆中当前元素个数 */int Capacity; /* 堆的最大容量 */ }; typedef Heap MaxHeap; /* 最大堆 */ typedef Heap MinHeap; /* 最小…

Θ(n)反转单链表(算法导论第三版第十章10.2-7)

Θ(n)反转单链表 (算法导论第三版第十章10.2-7) template<typename T> void reverses(Single_L<T> &l) {if(l.head nullptr || l.head l.tail) return;HalfNode<T> * tail l.head;HalfNode<T>*pre l.head;HalfNode<T>*current pre-&g…

C#跨平台开源项目实战(WPF/Android/IOS/Blazor)

个人介绍由于本人从业WPF开发, 考虑到国内的WPF开发环境并不是很好, 资源少、项目案例少, 所以导致很多初学者就已经断了念头。所以我作为WPF的从业者, 就在2019年,开始了发布自己的WPF相关的免费教学视频。发布开源的项目实践, WPF的基础视频、项目实践视频, 包括WPF UI设计视…

对象的多数组表示(不一样的链表-多数组表示链表)

对象的多数组表示 (不一样的链表-多数组表示链表&#xff09; // // Created by 许加权 on 2021/7/12. //#ifndef C11LEARN_MOSTGROUPSREPRESENTOBJECTS_H #define C11LEARN_MOSTGROUPSREPRESENTOBJECTS_H template<typename T> class MostGroupsRepresentObjects { pr…

[开源] .Net 使用 ORM 访问 神舟通用数据库(神通)

前言天津神舟通用数据技术有限公司&#xff08;简称“神舟通用公司”&#xff09;&#xff0c;隶属于中国航天科技集团&#xff08;CASC&#xff09;。是国内从事数据库、大数据解决方案和数据挖掘分析产品研发的专业公司。公司获得了国家核高基科技重大专项重点支持&#xff0…

对象的单数组表示(用单数组实现链表-不一样的链表实现)

对象的单数组表示 &#xff08;用单数组实现链表-不一样的链表实现&#xff09; #ifndef C11LEARN_SINGULARGROUPSREPRESENTOBJECTS_H #define C11LEARN_SINGULARGROUPSREPRESENTOBJECTS_H #include "cmath" #include "../tools/tool.h" template<typ…

图的建立-邻接矩阵表示(C语言)

代码如下: /* 图的邻接矩阵表示法 */#define MaxVertexNum 100 /* 最大顶点数设为100 */ #define INFINITY 65535 /* ∞设为双字节无符号整数的最大值65535*/ typedef int Vertex; /* 用顶点下标表示顶点,为整型 */ typedef int WeightType; /* 边的…

在 Xunit 中使用依赖注入

在 Xunit 中使用依赖注入Intro之前写过一篇 xunit 的依赖注入相关的文章&#xff0c;但是实际使用起来不是那么方便今天介绍一个基于xunit和微软依赖注入框架的“真正”的依赖注入使用方式 ——— Xunit.DependencyInjection, 来自大师的作品&#xff0c;让你在测试代码里使用依…