.NET Framework源码研究系列之---Delegate

前言

  曾几何时能看到微软产品的源码简直是天方夜谭,不过现在这却成了现实,微软终于对外开放了它的产品的源代码.抛去开源运动与微软之间的世代情仇,抛去微软这一做法的初衷,这总归是件好事,能够让我们拨开云雾,一窥优秀产品的秘密.

  前两天看到有位仁兄在随笔中的留言,说他以为".NET中的设计模式"是在讲.NET Framework与设计模式的关系,其实不是,不过这也让我想起来自己确实研究过.NET Framework的源码,于是就找打算找时间把自己的心得体会拿出来和大家一起分享.

  今天就先从最容易让人困惑的委托(delegate)开始,让我们步入.NET Framework源码世界,共同学习优秀的程序设计.

  先看委托的定义:用于声明一个引用类型,该引用类型可用于封装命名方法或匿名方法。委托类似于 C++ 中的函数指针;但是,委托是类型安全和可靠的。

  相信看到这段话之后,很多人,包括我自己就开始一起探索委托函数指针,于是各种网文就出现了.但委托到底是什么呢?我们先看一段很简单的代码:

 

public delegate void OnAction(int flag);

这里我们定义了一个最简单的委托:OnAction.MSDN解释Delegate 类是委托类型的基类,但只有系统和编译器可以显式地从 Delegate 类或 MulticastDelegate 类派生.那么我们可以认为OnAction是从delegate继承过来的,只是不能显式的继承,由系统代做了.

  接下来让我们看一下微软是怎么定义委托的:

 

[Serializable()]
[ClassInterface(ClassInterfaceType.AutoDual)]
[System.Runtime.InteropServices.ComVisible(
true)]
public abstract class Delegate : ICloneable, ISerializable

由此可以看出delegate是个抽象类,并且实现了 ICloneable, ISerializable两个接口,并且有ClassInterface(ClassInterfaceType.AutoDual)这么一个属性.这有很多问题.

  首先,委托是个抽象类,所以要使用的必须继承,但是委托又跟整型一样,是一种类型,由此就可以理解问什么"Delegate 类是委托类型的基类,但只有系统和编译器可以显式地从 Delegate 类或 MulticastDelegate 类派生"这句话的意思了,因为不可能从整型继承过来一个子类,那么委托为什么是一个类而不像整型一样是一个结构呢?这个问题下面回答.

  其次,这也是我觉得不理解的地方,委托实现了ICloneable, ISerializable两个接口,也就是说委托可以被克隆和序列化.相信大家没人会写OnAction.Clone();这么一句话.浅表克隆一个委托实在有点费解.不过,如果可以从良好的编程习惯上解释为什么事先ICloneable接口的话,委托对ISerializable的实现就更让人困惑了,因为可能因此导致一些莫名其妙的编译时错误.我曾经遇到这样一个Bug,一个标记序列化的单件实体类包含一个事件(事件默认实现了ISerializable),该事件导致序列化的时候在凡是跟该事件的引用有关的地方全部报出莫名其妙的未标记序列化的错误,最终的解决办法是需要将该事件标记为[NonSerialized].

      下面看一下具体是怎么实现委托的:

 

ContractedBlock.gifExpandedBlockStart.gif代码
public abstract class Delegate : ICloneable, ISerializable
{
//要调用的对象
internal Object _target;

// MethodBase, either cached after first request or assigned from a DynamicMethod
internal MethodBase _methodBase;

// _methodPtr is a pointer to the method we will invoke
// It could be a small thunk if this is a static or UM call
internal IntPtr _methodPtr;

// In the case of a static method passed to a delegate, this field stores
// whatever _methodPtr would have stored: and _methodPtr points to a
// small thunk which removes the "this" pointer before going on
// to _methodPtrAux.
internal IntPtr _methodPtrAux;

// This constructor is called from the class generated by the
// compiler generated code
protected Delegate(Object target, String method)
{
if (target == null)
throw new ArgumentNullException("target");

if (method == null)
throw new ArgumentNullException("method");

// This API existed in v1/v1.1 and only expected to create closed
// instance delegates. Constrain the call to BindToMethodName to
// such and don't allow relaxed signature matching (which could make
// the choice of target method ambiguous) for backwards
// compatibility. The name matching was case sensitive and we
// preserve that as well.
if (!BindToMethodName(target, Type.GetTypeHandle(target), method,
DelegateBindingFlags.InstanceMethodOnly
|
DelegateBindingFlags.ClosedDelegateOnly))
throw new ArgumentException(Environment.GetResourceString("Arg_DlgtTargMeth"));
}

// This constructor is called from a class to generate a
// delegate based upon a static method name and the Type object
// for the class defining the method.
protected unsafe Delegate(Type target, String method)
{
if (target == null)
throw new ArgumentNullException("target");

if (!(target is RuntimeType))
throw new ArgumentException(Environment.GetResourceString("Argument_MustBeRuntimeType"), "target");

if (target.IsGenericType && target.ContainsGenericParameters)
throw new ArgumentException(Environment.GetResourceString("Arg_UnboundGenParam"), "target");

if (method == null)
throw new ArgumentNullException("method");

// This API existed in v1/v1.1 and only expected to create open
// static delegates. Constrain the call to BindToMethodName to such
// and don't allow relaxed signature matching (which could make the
// choice of target method ambiguous) for backwards compatibility.
// The name matching was case insensitive (no idea why this is
// different from the constructor above) and we preserve that as
// well.
BindToMethodName(null, target.TypeHandle, method,
DelegateBindingFlags.StaticMethodOnly
|
DelegateBindingFlags.OpenDelegateOnly
|
DelegateBindingFlags.CaselessMatching);
}

// Protect the default constructor so you can't build a delegate
private Delegate()
{
}

上面代码显示委托类包含4个internal类型的字段,从其注释我们大致可以看出_target是我们要调用的对象,_methodBase是给委托赋值时传递的方法,_methodPtr是指向该方法的指针,_methodPtrAux同静态类型委托的methodPtr指向的内容一样,只是少了对象的引用. 在看委托类的三个构造函数,一个是私有的,是用来防止实例化的,另外2个差不多,做的事情是把一个方法绑定到一个对象上.这里的方法应该是我们赋给委托的方法,对象则是编译器自己来维护的对象.这里调用了一个方法BindToMethodName,在源码中看不到实现,仅能看到如下内容:

[MethodImplAttribute(MethodImplOptions.InternalCall)]
private extern bool BindToMethodName(Object target, RuntimeTypeHandle methodType, String method, DelegateBindingFlags flags);

MethodImplAttribute标签表明了该方法为CLR内部方法,具体是什么,我们不得而知.此类的方法还有很多,就不一一例举.说到这个地方,我们可以发现上面说的"OnAction是从delegate继承过来的"和"Delegate 类是委托类型的基类"并不正确,正确的理解应该是delegate类在内部管理了一个用于绑定方法的对象,这点也可以从OnAction反汇编出来的构造函数看出:

ContractedBlock.gifExpandedBlockStart.gif代码
.method public hidebysig specialname rtspecialname
instance
void .ctor(object 'object',
native
int 'method') runtime managed
{
}
// end of method OnAction::.ctor

另外delegate中还有一个静态方法:

public static Delegate CreateDelegate(Type type, Object firstArgument, MethodInfo method, bool throwOnBindFailure)

该方法前后有11个重载,其方法体里无一不包括:

 

ContractedBlock.gifExpandedBlockStart.gif代码
Delegate d = InternalAlloc(type.TypeHandle);
if (!d.BindToMethodInfo(firstArgument, method.MethodHandle, method.DeclaringType.TypeHandle,
DelegateBindingFlags.RelaxedSignature))
{
if (throwOnBindFailure)
throw new ArgumentException(Environment.GetResourceString("Arg_DlgtTargMeth"));
d
= null;
}

这样一段代码.其中InternalAlloc,BindToMethodInfo与BindToMethodName一样,都属于CLR内部方法.具体是什么一样看不出,只能大致猜测CLR内部生成了一个委托对象,然后将我们定义的委托签名,方法,方法引用等一些内容交给委托类来维护.

 

  通过以上分析,我们发现如它的定义,委托本质上是一个类,它的职责是代替我们管理维护方法调用.这一切无论从功能,还是外面,其实跟函数指针没有半点关系.至此,相信大家对delegate有了不一样的认识,同时也发现C#仅仅是CLR的一层外壳,并没有涉及到很核心的内容.

 

 

 

 

       以上仅仅是我个人的一点看法,由于本人水平有限,认识上难免有不正确的地方,如果你有更好的见解,敬请指正.

转载于:https://www.cnblogs.com/niyw/archive/2010/07/22/1783306.html

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

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

相关文章

如何设置多个图层层叠关系_如何玩转 XMind 中的多种思维结构?

熟悉 XMind 的用户都知道,XMind 支持多种思维结构,并且不同思维结构可以混用。每一个分支都可以是一个不同的结构,让你不受限制、自由地进行思维的发散和整理。这个是目前其他思维导图工具少有的。在 XMind 中,你可以用思维导图、…

python修改html表格,使用styles和css更改pandas dataframe html表python中...

这需要几个步骤:首先导入HTML并重新输入from IPython.display import HTMLimport re你可以通过to_html方法得到html pandas.df_html df.to_html()接下来,我们将为html表和我们要创建的样式生成随机标识符.random_id id%d % np.random.choice(np.arange(1000000))因…

PHP关于VC11,VC9,VC6以及Thread Safe和Non Thread Safe版本选择

2019独角兽企业重金招聘Python工程师标准>>> 这里是我在搭建php环境时收集的资料供大家参考: 现在PHP官网上下载PHP安装包都有VC11或VC9的字样,这是什么含义,我们应该下载哪种安装包更好呢?其实PHP官网给出了答案&…

Silverlight与WCF之间的通信(5)silverlight应用和wcf服务的发布方法

上一篇博文中有朋友问到关于silverlight程序发布的问题,上一篇写的是silverlight访问host在console上的wcf,其实关于wcf和silverlihgt通信的问题有好几种方式,这里列举了一下 客户端和服务端采用http协议通信(分两种,同…

小程序引用其他页面js_来聊聊小程序页面之间如何通信

小程序页面之间如何通信?首先将通信的模型列举出来, 分为以下几种兄弟页面间通信父路径页面向子路径页面通信子路径页面向父路径页面通信通信的方式localStorage 本地存储globalData 全局对象eventBus 发布订阅PageModel 缓存整个pageModel至globalDataLocalStorage利用onShow…

用碧海潮声制作的宋体(雅黑宋体)替换Windows7原生的火柴棍式的宋体

第一眼见到Windows7的时候,界面真的非常漂亮,但是当查看文件属性的时候,里面宋体出奇的难看,网上有很多在XP里替换宋体的方法,但是到了Windows7里就不那么好用了,经过多次查找相关方法,终于使用…

json动画_three.js动画(四)

ThreeJS的动画系列分为:基础动画、相机控制、变形动画、用骨骼和蒙皮制作动画以及使用外部模型创建动画。用骨骼和蒙皮制作动画用骨骼来做动画时,移动一下骨骼,Three.js必须决定如何相应地迁移附着在骨骼上的皮肤,一起来看吧~~~举…

session很快失效_一口气说出 4 种分布式一致性 Session 实现方式,面试杠杠的~

前言公司有一个 Web 管理系统,使用 Tomcat 进行部署。由于是后台管理系统,所有的网页都需要登录授权之后才能进行相应的操作。起初这个系统的用的人也不多,为了节省资源,这个系统仅仅只是单机部署。后来随着用的人越来越多&#x…

定义动画名字html,CSS3 animation-name属性怎么用?

css3 animation-name属性是用来检索或设置对象所应用的动画名称,必须与keyframes配合使用,因为动画名称由keyframes定义 ;如果有多个属性值,可以用逗号进行分隔。css3 animation-name属性作用:animation-name 属性为 k…

线程中如何使用对象_多线程中如何使用gdb精确定位死锁问题

在多线程开发过程中很多人应该都会遇到死锁问题,死锁问题也是面试过程中经常被问到的问题,这里介绍在c中如何使用gdbpython脚本调试死锁问题,以及如何在程序运行过程中检测死锁。首先介绍什么是死锁,看下维基百科中的定义&#xf…

html中如何把两行合并单元格,css合并两列单元格内容

用纯DIVCSS做一个两行两列的表格,但第二列中两行怎么在html里把一行中的两列合并世界最不可以相信的话,就是从女人嘴里说出的话〃如上图,怎么做出上面图中的效果,分享大神详解CSS表格单元格占两行可以参考以下的代码: 单元格占两行…

ASP.NET MVC 实现二级域名(泛域名)

自从微软发布 ASP.NET MVC 和routing engine (System.Web.Routing)以来,就设法让我们明白你完全能控制URL和routing,只要与你的application path相结合进行扩展,任何问题都迎刃而解。如果你需要在所处的域或者子域处理数据标记的话&#xff0…

list和tuple

2019独角兽企业重金招聘Python工程师标准>>> list Python内置的一种数据类型是列表:list。list是一种有序的集合,可以随时添加和删除其中的元素。 比如,列出班里所有同学的名字,就可以用一个list表示: >…

springboot数据源不正确_Spring MVC 到 Spring Boot 的简化之路

Spring全家桶笔记:SpringSpring BootSpring CloudSpring MVC​shimo.im01 背景从Servlet技术到Spring和Spring MVC,开发Web应用变得越来越简捷。但是Spring和Spring MVC的众多配置有时却让人望而却步,相信有过Spring MVC开发经验的朋友能深刻…

MapXtreme 包含所有自带坐标系一览

CoordSys 对象包含关于 X 和 Y 坐标如何与其在 Earth 上的位置相关联的基本信息。 每个 Geometry 或 Map 对象都有一个关联的坐标系。 CoordSys 对象包含对坐标系的详细说明。 CoordSysFactory 类提供了各种用于创建不同 CoordSys 对象的方法。 所有 CoordSys 对象都是只读的&a…

如何学习streamdecoder类_2019年终巨献:一份拿下了阿里、网易、滴滴等大厂offer的学习笔记...

2019仅剩最后二十天,回顾今年初遇“寒冬”时,自己也挺慌的,但是经历过这么多次面试后,我才“醒悟”,所谓的“寒冬”,“冻死”的都是“衣服穿的少的”。年末了在这里做一个年度总结,今年面试了不…

自定义控件的构建(12)

Share 前面讲了模板的构建&#xff0c;我们忽略了一个细节&#xff0c;如果接触ASP.NET时间不长的话&#xff0c;一般都会看到数据表达式是<%#Eval(‘Name’)%>这种形式的&#xff0c; 那么我们为什么用<%#Container.Name%>这种形式呢&#xff0c;其实前者是ASP.NE…

insertAfter()

<div id"b">bbbbbbbbb</div> <div>dddddd</div> JavaScript window.οnlοadfunction(){var a document.createElement("span");var b document.createTextNode("cssrain");a.appendChild(b);var mubiao document.getE…

学计算机等级考试电脑版软件,计算机二级考试宝典电脑版

计算机二级考试宝典电脑版是一款专业的二级计算机内容学习软件。该软件由武汉大学团队真情研发&#xff0c;软件包含选择题1600道&#xff0c;非选择题109套&#xff0c;成功实现了考点和重点的全面覆盖式学习目的&#xff0c;对学生们学习起到了巨大的帮助。该版本是通过安卓模…

mysq进阶

学习资料&#xff1a; 官方文档&#xff1a;http://dev.mysql.com/doc/refman/5.0/en/tutorial.html 1.存储过程&#xff1a; 优点&#xff1a;业务逻辑封装在存储过程中&#xff0c;容易维护&#xff0c;执行效率也高。 缺点&#xff1a;不同的数据库功能函数等不一样&#xf…