【C#进阶系列】17 委托
委托主要是为了实 现回调函数机制,可以理解为函数指针(唯一不同的在于多了委托链这个概念)。
然而用的时候可以这么理解,但是委托的内部机制是比较复杂的。
一个委托的故事
delegate void razor(String userName);
一个简单的委托被定义了。
实际上在编译后这段代码就和下面的代码很像了:
class Razor : System.MulticastDelegate {
//构造函数
public Razor(Object @object,IntPtr method);
public virtual void Invoke(Int32 value);
//实现对回调方法的异步回调
public virtual IAsyncResult BeginInvoke(Int32 value, AsyncCallback callback, Object @object);
public virtual void EndInvoke(IAsyncResult result);
}
也就是说委托实际上是一个派生自MulticastDelegate的类,其中有四个方法。
而由于继承MulticastDelegate类,所以还继承了MulticastDelegate类的字段、方法和属性。
有三个字段最重要,分别是指向委托传进的实例方法的操作对象_target(如果是静态方法就是NULL),CLR用来标识回调方法的_methodPtr(这里不是指针,而是一个内部的参数值,用以表示某方法),构造委托链时委托数组的引用_invokationList(在委托通过Delegate.Combine或者+=绑定了多个回调函数后才有值,否则为null).
而在使用委托的时候会:
先去看此委托的_invokationList是否为null,为null就说明没有建立委托链,那么实际上就用了委托Razor(实际上是一个类的实例),调用Invoke方法,此时会传给Invoke方法一个参数值,它就是_methodPtr存放的内部的参数值,用于标识传进来的实例方法的参数值。
如果_invokationList不为null说明已经建立了委托链,那么久分别去调用委托数组里面各个委托的Invoke方法,执行的方法自然是各个委托里面_methodPtr指定的方法。
而至于移除委托(用Delegate.Remove或者-=)。
执行委托链中的函数是按照绑定的顺序开始执行,但是如果有返回,那么返回的一定是最后一个绑定的函数的返回结果。
然而这种执行委托链的方式有一些问题,比如某个委托链中的委托抛了异常或者因为调用数据库查询的阻塞时间太长,导致后面的委托函数都无法执行。
所以MulticastDelegate类提供了一个实例方法GetInvokationList(),他会返回_invokationList指向的Delegate数组。
泛型委托
泛型委托是为了解决相似的委托过多的问题,而且.NET提供的泛型委托也很简单,17个参数不同的Action委托和17个参数不同的Func委托.
其中Action委托都是返回值为void,而Func委托的返回值类型为自己指定的类型的TResult。
然而如果需要使用ref和out关键字以传引用的方式传递参数,那么就不得不定义自己的委托。因为泛型委托的协变和逆变的时候会用到in和out标识,为了搞混就不能这么用。(此处可参考泛型那一章)
C#关于委托的语法糖
一个正常的委托是下面这样的
static void Main(string[] args)
{
Razor razor=null;
razor += new Razor(Blower);
razor("Troy123");
Console.Read();
}
static void Blower(String userName) {
Console.WriteLine(userName + ":实际上这是一个吹风机");
}
然而
razor += new Razor(Blower);
这样的语法看起来很奇怪(一开始奇怪,其实还好啦),所以C#提供个一些简化的语法:
- 不需要构造委托对象
一些函数会像下面那样可以直接传入回调方法,而不是像之前一样需要new Razor这样去调用构造委托对象。表面上是如此,实际上只是在内部做了处理,还是会去构造委托对象然后调用。
void 回调函数(object obj) {
Console.WriteLine(obj);
}
ThreadPool.QueueUserWorkItem(回调方法, );
- 不需要定义回调方法(lambda表达式)
- 之前的代码可以写成这样
ThreadPool.QueueUserWorkItem(l=>Console.WriteLine(o),);
- lambda表达式这种方法看起来很怪异,然而用习惯了非常好用。这是通过创建匿名函数去取代回调函数。(这种玩法虽然好,但是如果一个回调函数被多个地方调用,那么还是用之前的方法好一点)
- 匿名函数一般被标记为static和private。前者是因为不需要创建额外的this参数所以效率更高,后者是为了安全性。(除非匿名函数用了实例成员)
- 以下是一些具体例子:
delegate void Bar(out Int32 z); Func<string> f = () => "Troy";
Func<Int32,string> f1 = (Int32 l) => l.ToString();
Func<Int32,string> f2 = (l) => l.ToString();//推断类型
Func<Int32, Int32, string> f3 = (l, m) => { Console.WriteLine(m); return l.ToString(); };//多条语句加大括号
Bar b = (out Int32 n) => n = ;//对于引用类型,必须显示指定
- 以下是一些具体例子:
- 之前的代码可以写成这样
- 局部变量不需要手动包装到类中即可传给回调方法
- 看起来很绕,实际上很简单。就是lambda表达式可以直接使用局部变量和参数,而不需要你去传参。
委托和反射
System.Delegate.MethodInfo提供了一个CreateDelegate方法,顾名思义,就是去动态创建一个委托。
然后可以用Delegate.DynamicInvoke方法去动态调用它。
关于这个具体的也没有想到太多的地方会用到,毕竟见的世面少了。
【C#进阶系列】17 委托的更多相关文章
- JavaScript进阶系列06,事件委托
在"JavaScript进阶系列05,事件的执行时机, 使用addEventListener为元素同时注册多个事件,事件参数"中已经有了一个跨浏览器的事件处理机制.现在需要使用这个 ...
- Ext JS学习第十六天 事件机制event(一) DotNet进阶系列(持续更新) 第一节:.Net版基于WebSocket的聊天室样例 第十五节:深入理解async和await的作用及各种适用场景和用法 第十五节:深入理解async和await的作用及各种适用场景和用法 前端自动化准备和详细配置(NVM、NPM/CNPM、NodeJs、NRM、WebPack、Gulp/Grunt、G
code&monkey Ext JS学习第十六天 事件机制event(一) 此文用来记录学习笔记: 休息了好几天,从今天开始继续保持更新,鞭策自己学习 今天我们来说一说什么是事件,对于事件 ...
- C#进阶系列 ---- 《CLR via C#》
[C#进阶系列]30 学习总结 [C#进阶系列]29 混合线程同步构造 [C#进阶系列]28 基元线程同步构造 [C#进阶系列]27 I/O限制的异步操作 [C#进阶系列]26 计算限制的异步操作 ...
- C#进阶系列——WebApi 路由机制剖析:你准备好了吗?
前言:从MVC到WebApi,路由机制一直是伴随着这些技术的一个重要组成部分. 它可以很简单:如果你仅仅只需要会用一些简单的路由,如/Home/Index,那么你只需要配置一个默认路由就能简单搞定: ...
- C#进阶系列——MEF实现设计上的“松耦合”(一)
前言:最近去了趟外地出差,介绍推广小组开发的框架类产品.推广对象是本部门在项目上面的同事——1到2年工作经验的初级程序员.在给他们介绍框架时发现很多框架设计层面的知识他们都没有接触过,甚至没听说过,这 ...
- 【 D3.js 进阶系列 】 进阶总结
进阶系列的文章从去年10月开始写的,晃眼又是4个多月了,想在年前总结一下. 首先恭祝大家新年快乐.今年是羊年吧.前段时间和朋友聊天,聊到十二生肖里为什么没猫,我张口就道:不是因为十二生肖开会的时候猫迟 ...
- Wireshark入门与进阶系列(二)
摘自http://blog.csdn.net/howeverpf/article/details/40743705 Wireshark入门与进阶系列(二) “君子生非异也,善假于物也”---荀子 本文 ...
- 深入理解javascript函数进阶系列第一篇——高阶函数
前面的话 前面的函数系列中介绍了函数的基础用法.从本文开始,将介绍javascript函数进阶系列,本文将详细介绍高阶函数 定义 高阶函数(higher-order function)指操作函数的函数 ...
- DotNet进阶系列
一. 回顾历史 回顾个人发展历程,自2012年初次接触开发至今(2018年)已经有六个年头,这期间陆陆续续学习并掌握了不少技术,C#语言.ORM框架.多线程技术.设计模式.前端技术.MVC.MVVM框 ...
- JavaScript进阶系列07,鼠标事件
鼠标事件有Keydown, Keyup, Keypress,但Keypress与Keydown和Keyup不同,如果按ctrl, shift, caps lock......等修饰键,不会触发Keyp ...
随机推荐
- 微软Edge 内嵌的JavaScript 引擎即将开源
微软于今日(2015年12月10日)宣布即将开源Chakra核心控件,并改名为“ChakraCore”,该控件包含所有Edge JavaScript 引擎的所有核心功能.ChakraCore 将于下月 ...
- MYSQL-用户操作
说明:本文主要写了,MYSQL对于用户的一些操作,有:查看用户,创建用户,权限的赋予,收回,用户的密码修改和删除. MySql的用户管理是通过 User表来实现的,添加新用户常用的方法有两个,一是在U ...
- swift NSComparator
var cmptr:NSComparator = {(obj1:AnyObject!, obj2:AnyObject!) -> NSComparisonResult in if((obj1[&q ...
- Laravel5.0学习--02 实例进阶
本文以laravel5.0.22为例. 本节以新建一个简单的博客作为实例. 准备工作 数据库配置 .env文件(也可以直接修改config/database.php) DB_HOST=localhos ...
- Atitit 编程语言知识点tech tree v2 attilax大总结
Atitit 编程语言知识点tech tree v2 attilax大总结 大分类中分类小分类知识点原理与规范具体实现(javac#里面的实现phpjsdsl(自己实现其他语言实现 类与对象实现对象实 ...
- Atitit 文档资料管理同步解决方案
Atitit 文档资料管理同步解决方案 流程 生成快照, 操作. 使用快照与dir对比,生成操作日志 /atiplat_cms/src/com/attilax/sync/SyncUtil.java p ...
- Entity Framework 5中应用表值函数进行Linq查询
Entity Framework 5引入了表值函数(Table-Valued Functions TVFs).表值函数的返回类型是一个Table类型,可用在SQL查询语句中.最简单的表值函数,读取客户 ...
- CSS技能汇总,研究及实践
最近一直在研究CSS,因为发现实践中大部分时间都在写CSS,且自己感觉写的很烂,虽然以前看的很多,但却很少有去实践过,更别提研究了,现在发现根本就不是你懂你就会,很多都是你用着用着才真的会了的,于是现 ...
- 使用Source Safe for SQL Server解决数据库版本管理问题
简介 在软件开发过程中,版本控制是一个广为人知的概念.因为一个项目可能会需要不同角色人员的参与,通过使用版本控制软件,可以使得项目中不同角色的人并行参与到项目当中.源代码控制使得代码可以存在多 ...
- 仿SGI STL的traits技法
首先是iterator traits,这个是用来萃取迭代器的特性的 #ifndef _STL_ITERATOR_H_ #define _STL_ITERATOR_H_ #include <cst ...