.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,一经查实,立即删除!

相关文章

【原】P2P应用的探究

什么是P2P(peer to peer)? 跟P2P对应的是传统的客户端-服务器(C/S或B/S)体系结构,这种体系结构就是客户端发送请求,服务器端给予响应。但是随着客户端不断的增加,成了服务器端崩溃的主要原因。增加服务器的功能或者资…

如何设置多个图层层叠关系_如何玩转 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协议通信(分两种,同…

常用函数(字符和字符串)

Pascal常用的字符处理标准函数有5个 (1)ord(ch) 求字符ch对应的ASCII代码值;如 ord (A)结果为65,ord(true)结果为1,ord(false)结果为0(2)chr(x) 求x(x为1…255…

python搜索文件内容_python实现搜索文本文件内容

{"moduleinfo":{"card_count":[{"count_phone":1,"count":1}],"search_count":[{"count_phone":4,"count":4}]},"card":[{"des":"阿里云文件存储NAS是一个可共享访问&#xf…

html单张图片效果,jquery+html5实现单张图片上传预览

js:if (window.File && window.FileReader && window.FileList && window.Blob){//Blob是计算机界通用术语之一,全称写作:BLOB (binary large object),表示二进制大对象。//全部支持function handleFileS…

通向成功的23个方法

1.设立大目标和小目标。总要保持向前看,但不要忘记你今天的成绩。 2.每天都设立新的目标。因为成功是一个过程,所以要确保总是有新的事情要完成。 3.逐一完成每周的事情。将时间表分成容易做到的几块,并且单独完成每一块。 4.每天至少完成一件…

科讯SQL标签调用文章技巧

http://qdh68.blog.163.com/blog/static/13756126201241584835332/转载于:https://www.cnblogs.com/zhangzhu/archive/2013/03/31/2991754.html

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

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

html meta页面自适应,【转载·收藏】 html5手机网站自适应需要加的meta标签

webapp开发初期,会碰到在pc端开发好的页面在移动端显示过大的问题,这里需要在html head中加入meta标签来控制缩放下面是对于这个标签的具体说明:viewport 语法介绍:content包含有以下几个属性:height [pixel_value | device-height] ,width [pixel_value | device-w…

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

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

最近2个月的生活

最近2个月,心情动荡不定。时而快乐,时而悲伤,时而激动,时而淡定。最近的故事还是比较多的,就按编号来分别描述吧.1.周五,公司举办了2012年年会。 本次年会,是 帅哥和美女展示身材和才艺的机会。…

json动画_three.js动画(四)

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

如何做PHD (1)

做PHD两年了,经验不多,从朋友、导师、教授、名人得到的做PHD的经验却很宝贵,为了不让自己忘记,特此记录,希望对各位已经是PHD或即将成为PHD的朋友有所帮助。 1.多读文章 做PHD与做硕士不一样,计算机的硕士…

sqlite3x library

sqlite3x - C wrapper of SQLite API. http://sourceforge.net/projects/int64/files/SQLite3%20C%2B%2B%20Wrapper/ https://github.com/ptrv/sqlite3x http://wanderinghorse.net/computing/sqlite/ Appendix http://www.sqlite.org/转载于:https://www.cnblogs.com/androidm…

html5监听动画结束,js判断css动画是否完成 animation,transition

气死了,发现这些鸟人讲事情都讲一半,害死初学者。css动画有两种,animation,transition,所以分开来讲。1、animation:css定义 #left1{}html定义:安全那么我们就用#left1来获取元素:/* 监听变换事…

[算法]不用第三个数交换2个数的位置

int a 10; int b 20; 第一种方法: a a b; b a - b; a a - b; 第二种方法: a a ^ b; (^ 语言中是异或的意思,同为0 异为1 1 ^ 1 0, 0 ^ 0 0, 1 ^ 0 1) b a ^ b; a a ^ b; 第三种方法 (这种方法容易越界&#xff09…

四十之后才明白

四十以后才明白:好朋友是应该相互欣赏的,而不是相互利用的。 四十以后才明白:饭应一口一口吃,事要一点一点做。没有一蹴而就的事情,所以就不应该疲于奔命。中年,应该活得从容。 四十以后才明白&#xff1a…