c#url拼接方法名_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(){ //一个HR HR 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(){ //一个HR HR 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(){ //一个HR HR 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只对应一个方法 ):
public class HR1{ public void Delegate(SendDelegate sendDelegate) { sendDelegateList = new List { sendDelegate }; //对应= } public void AddDelegate(SendDelegate sendDelegate) { sendDelegateList.Add(sendDelegate); //对应+= } public void RomoveDelegate(SendDelegate sendDelegate) { sendDelegateList.Remove(sendDelegate);//对应-= } public List 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 模拟了多播委托的绑定。 会想到如果真能循环调用一个个已绑定的委托,就可以精确的进行控制了。那么这里说一下这样的方法:
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 message);
这样的委托适用于不同的参数类型,例如如下代码(注意使用的时候要对应具体的类型)
public delegate string SendDelegate(T message);public class HR1{ public SendDelegate<string> sendDelegate1; public SendDelegate<int> sendDelegate2; public SendDelegate 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 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
.NET Core 版本中,EventHandler
定义不再要求 TEventArgs
必须是派生自 System.EventArgs
的类, 使我们使用起来更为灵活。
例如我们可以有这样的写法:
EventHandler<string> SendNew
这在以前的版本中是不允许的。
c#url拼接方法名_C# 从1到Core委托与事件相关推荐
- c#url拼接方法名_c# 获取url参数的五种方法
假设当前页完整地址为:http://www.jbxue.com/aaa/bbb.aspx?id=5&name=kelli 则: "http://"是协议名 "ww ...
- PHP 获取当前类名、方法名、URL地址
1.PHP获取当前类名.方法名 __CLASS__ 获取当前类名 __FUNCTION__ 当前函数名(confirm) __METHOD__ 当前方法名 (bankcard::con ...
- CI框架获取控制器名和方法名
CI获取控制器名和方法名 第一种方法: 即使有__CLASS__,__FUNCTION__魔术变量,但是在父类控制器,或者在model中,只能获取当前类的类名和方法名. 第二种方法: $this-&g ...
- ThinkPHP 3.2 中获取所有函数方法名,以及注释,完整可运行
<?php namespace Home\Controller; use Common\Controller\BaseController; class AuthController exte ...
- python 获取当前class名和方法名
代码如下: # coding=utf-8import sys class Hello():def hello(self):print('the name of method is ## {} ##'. ...
- 改变客户端访问时的方法名
•客户端无法重载方法(overload) –可以通过判断arguments数量来模拟重载 •如果服务器端出现了方法重载? –使用WebServiceAttribute指定客户端方法名 –使用和真正的W ...
- JAVA中获取当前运行的类名,方法名,行数
JAVA中获取当前运行的类名,方法名,行数 public static String getTraceInfo(){ StringBuffer sb = new StringBuffer(); Sta ...
- 【转】获取命名空间、类名、方法名
string str = "";//取得当前方法命名空间str += "命名空间名:" + System.Reflection.MethodBase.GetCu ...
- c# 获取方法所在的命名空间 类名 方法名
平时我们在记录日志的时候难免会需要直接记录当前方法的路径,以便查找,但是每次都输入方法名称非常的繁琐,同时如果修改了方法名称也要去手动修改日志内容,真的是劳命伤财啊,所以有了如下方法则可解决我们的大难 ...
最新文章
- nginx 的proxy_cache才是王道
- 【数字信号处理】基本序列 ( 实指数序列 | 收敛序列 | 发散序列 )
- yum(Fedora和RedHat以及SUSE中的Shell前端软件包管理器)命令详解
- C/C++ 指针的深入理解
- html2canvas在安卓端微信里截取从相册打开的图片空白问题
- 从HP收购ArcSight看SIEM/MSS市场现状与格局【9月17日更新】
- nginx日志统计分析的相关常用命
- jw player 5去掉share,info,embed页面
- [CF296D] Greg and Graph [floyd]
- 上周热点回顾(10.18-10.24)
- 种子点生长算法(上)——二维种子点生长
- 【情人节表白神器:送她一个HTML动态表白网站 带源码】
- JDBC--藤原豆腐店自用
- 六边形算法java_六边形架构 Java 实现
- 【PAT】B1032 挖掘机技术哪家强 (20 分)_C语言实现
- Web开发模式的探讨
- 使用杉川3i-T1单线激光雷达和Cartographer库SLAM问题及解决
- 墨西哥区域相关的西班牙语日期的处理方案。。
- wintogo与多PE合盘
- 网际风全推数据接口调用规范3.0 版
热门文章
- ubuntu ip设置
- VOIP,PSTN,ISDN
- VMWare网络设置的3中方式
- linux tao环境 安装_Linux安装jdk8及环境变量配置
- websocket多人聊天php,php-notes/基于websocket实现多人聊天室.md at master · dd-code-site/php-notes · GitHub...
- java 服务降级_微服务的降级学习
- python selenium api_Selenium2+python自动化-查看selenium API
- Qt: QTableView如何获取(行)选中、行切换信息
- 【LeetCode-SQL每日一练】—— 1179. 重新格式化部门表
- 【爱心代码大全】——情人节表白代码送给她属于我们程序员的浪漫