[C# 基础知识系列]专题二:委托的本质论 (转载)
引言:
上一个专题已经和大家分享了我理解的——C#中为什么需要委托,专题中简单介绍了下委托是什么以及委托简单的应用的,在这个专题中将对委托做进一步的介绍的,本专题主要对委本质和委托链进行讨论。
一、委托的本质
平时我们很容易使用委托——用C# delegate关键字定义委托,再用new操作符构造委托实例,然后通过调用委托实例来调用回调方法(就是用一个了委托对象的变量来代替方法名,这句话如果刚接触的人不好理解的话,这里给个例子:MyDelegate mydelegate =new Mydelegate(obj.mymethod),MyDelegate 是定义的一个委托,假设定义的是没有参数的,然后调用委托实例是这样的——mydelegate(), 大家可以发现此时调用委托和调用方法的方式是一模一样的,如果没有看前面mydelegate是个委托类型,大家都会认为这是直接调用一个方法,而不是调用委托实例,通过这个例子大家应该很容易明白了这句话了吧——用一个委托对象的变量来代替方法名),相信通过括号内的讲解后,相信大家又会对委托有进一步的理解的——委托就是方法的代替品,委托变量此时着方法名,大家可以简单理解委托是方法的一个 “外号”。
前面的都介绍了委托的一些使用和理解的,现在就让我我们来进一步看看编译器和CLR在背后对我们用delegate 关键字定义的委托类型做了些什么事情的,前一个专题中我和大家说过委托是一个类,这么是有根据的,因为我们在IDE中定义一个委托类型时,最终是通过编译器将定义的代码转化为中间语言IL,然后再执行中间语言中的代码来转化为本机代码的,所以在Visual Studio中编写的代码只是一个包装而已,真真程序执行的是中间语言中的代码的。现在就看看编译器把我们定义的委托类型转化为什么样的中间语言代码的。
当我们在类中像下面这样定义一个委托时:
public delegate void DelegateTest(int parm);
编译器把我们定义的委托类型编译成一个下面这样的类:

Public class DelegateTest: System.MulticastDelegate
{
public DelegateTest(Object object, IntPtr method);
public virtual Void Invoke(int32 parm);
public virtual IAsyncResult BeginInvoke(Int32 parm, AsyncCallback callback, Object object );
public virtual void EndInvoke(IAsyncResult result);
}

从中间语言的代码就可以很明显的看出我们在代码中写的委托,对于中间语言来说就是一个类,该类继承于FCL中定义的Systme.MulticastDelegate类型,所有委托类型都派生于MulticastDelegate,该类中还定义了四个方法,一个构造函数,Invoke方法,还有就是两个异步方法BeginInvoke和EndInvoke方法,关于这两个异步方法,大家可以查看我博客中的线程系列。大家可以用ILDasm.exe工具去查看委托生成的中间代码,下面我截的一个图(从我们定义的DelegateTest的前面的图标和我们主程序传递Program的图标是一样的,然而Program是一个类,很明显定义的委托DelegateTes也是一个类的):

由于所有委托类型都是继承于MulticastDelegate,MulticastDelegate又继承于Delegate,所以委托类型继承了MulticastDelegate的字段、属性和方法,在这些成员中,有三个非公共字段与后面专题要介绍的委托链有关,所以在这里先列出来的:
|
字段 |
类型 |
解释 |
|
_target |
System.Object |
当委托对象包装的是一个静态方法时,这个字段为null,当委托对象包装一个实例方法时,这个字段引用的是方法所在的类的对象 |
|
_methodPtr |
System.IntPtr |
一个内部的整数,可以认为是方法句柄,标识着要调用的方法 |
|
_invocationList |
System.Object |
该字段通常为null,当构造一个委托链(多播委托)时,才引用一个委托数组。具体下一部分讲解。 |
大部分人可能会有这么个疑问,既然是非公共字段,所以在MSDN上是看不到的,那我是怎么知道有这三个字段的呢?大家可以通过Reflector工具是反编译查看源码,Multicastdelegate 类通过MSDN查找可以知道该类的命名空间和程序集,这样就可以更具程序集和命名空间用Reflector工具查看Multicastdelegate类的源码,下面是我用Reflector这个工具查看到的源码截图:

从截图中可以看出MulticastDelegate 类中只有两个字段,却没有前面表格中列出的_methodPtr和_target字段的,这两个字段是定义在Delegate类中,大家使用Reflector工具来查看的,这里就不具体贴图了,文章最后会给出Reflector工具下载链接的。
委托对象就是一个包装器,包装了一个方法和调用该方法时要操作的对象,例如,执行下面的代码时:

public class Program
{
// 声明一个委托类型,它的实例引用一个方法
// 该方法回去一个int 参数,返回void类型
public delegate void DelegateTest(int parm);
public static void Main(string[] args)
{
// 用静态方法来实例化委托
DelegateTest dtstatic = new DelegateTest(Program.method1);
// 用实例方法来实例化委托
DelegateTest dtinstance = new DelegateTest(new Program().method2);
}
private static void method1(int parm)
{
Console.WriteLine("调用的是静态方法,参数值为:" + parm);
}
private void method2(int parm)
{
Console.WriteLine("调用的是实例方法,参数值为:" + parm);
}
}

代码中dtstatic 和dtinstance变量引用了初始化好的DelegateTest委托对象,此时这两个委托对象的上面列出来的三个字段初始化情况如下图:

二、总结
本专题从中间语言的角度去详细解析定义的委托类型经编译器转化后的的中间语言是怎样来解释一个委托类型的,得到的结论是——委托实际上是一个类,该类派生于MulticastDelegate类,且继承了该类的_target,_methodPtr和_invocationList这三个字段,当我们初始化一个委托对象时,此时就会先初始化这三个字段,对于包装实例方法和静态方法的委托,初始化这三个字段也有所不一样,在上面的截图中也所体现,这里引用了一个很重要的字段——_invocationList(即委托实例的调用列表),对于委托对象包装一个方法时,该字段为null,如果委托对象要包装多个方法时,此时_invocationList字段就会被初始化为引用一个委托对象的数组(就是指向委托对象的一个集合),具体这方面的内容将在下一专题介绍委托链中为大家详细介绍。 到这里,本专题的内容也结束了,希望通过本专题,大家可以更进一步的理解C#中的委托。
[C# 基础知识系列]专题二:委托的本质论 (转载)的更多相关文章
- 方法字段[C# 基础知识系列]专题二:委托的本质论
首先声明,我是一个菜鸟.一下文章中出现技术误导情况盖不负责 引言: 上一个专题已和大家分享了我懂得的——C#中为什么须要委托,专题中简略介绍了下委托是什么以及委托简略的应用的,在这个专题中将对委托做进 ...
- [C# 基础知识系列]专题一:深入解析委托——C#中为什么要引入委托
转自http://www.cnblogs.com/zhili/archive/2012/10/22/Delegate.html 引言: 对于一些刚接触C# 不久的朋友可能会对C#中一些基本特性理解的不 ...
- [C# 基础知识系列]专题三:如何用委托包装多个方法——委托链 (转载)
引言: 上一专题介绍了下编译器是如何来翻译委托的,从中间语言的角度去看委托,希望可以帮助大家进一步的理解委托,然而之前的介绍都是委托只是封装一个方法,那委托能不能封装多个方法呢?因为生活中经常会听到, ...
- 链方法[C# 基础知识系列]专题三:如何用委托包装多个方法——委托链
最近研究链方法,稍微总结一下,以后继续补充: 弁言: 上一专题分析了下编译器是如何来翻译委托的,从中间语言的角度去看委托,希望可以帮助大家进一步的理解委托,然而之前的分析都是委托只是封装一个方法,那委 ...
- [C# 基础知识系列]专题九: 深入理解泛型可变性
引言: 在C# 2.0中泛型并不支持可变性的(可变性指的就是协变性和逆变性),我们知道在面向对象的继承中就具有可变性,当方法声明返回类型为Stream,我们可以在实现中返回一个FileStream的类 ...
- [C# 基础知识系列]专题四:事件揭秘
转自http://www.cnblogs.com/zhili/archive/2012/10/27/Event.html 引言: 前面几个专题对委托进行了详细的介绍的,然后我们在编写代码过程中经常会听 ...
- [C# 基础知识系列]专题四:事件揭秘 (转载)
引言: 前面几个专题对委托进行了详细的介绍的,然后我们在编写代码过程中经常会听到“事件”这个概念的,尤其是写UI的时候,当我们点击一个按钮后VS就会自动帮我们生成一些后台的代码,然后我们就只需要在Cl ...
- [C# 基础知识系列]专题八: 深入理解泛型(二)
引言: 本专题主要是承接上一个专题要继续介绍泛型的其他内容,这里就不多说了,就直接进入本专题的内容的. 一.类型推断 在我们写泛型代码的时候经常有大量的"<"和"& ...
- [C# 基础知识系列]专题七: 泛型深入理解(一) (转载)
引言: 在上一个专题中介绍了C#2.0 中引入泛型的原因以及有了泛型后所带来的好处,然而上一专题相当于是介绍了泛型的一些基本知识的,对于泛型的性能为什么会比非泛型的性能高却没有给出理由,所以在这个专题 ...
随机推荐
- Myeclipse在debug模式下没加断点程序卡住,start模式下可以正常启动
参考<eclipse在debug模式下卡住,start模式下可以启动>,地址:https://blog.csdn.net/jack_chen1994/article/details/761 ...
- Vue项目本地run与build后样式不同,build后样式不生效
今天老大,让我改一个按钮的样式,就是鼠标放在按钮上,改变字体的颜色.觉得小意思啦,不就是:hover吗? 啊...什么鬼?本地run可以,但是build之后并没有生效!!! 我们项目引入的第三方UI库 ...
- Form表单发送到服务器时的编码方式
---恢复内容开始--- 表单中的表单中enctype是设置表单的MIME编码. 所谓MIME编码,是指当服务器传送数据给客户端时,必须指定这个文件是什么类型,才能方便客户端调用相应的应用软件来打开该 ...
- oracle如何调试存储过程
oracle如果存储过程比较复杂,我们要定位到错误就比较困难,那么可以存储过程的调试功能 先按简单的存储过程做个例子,就是上次做的存储过程(proc_test) 1.先在数据库的procedures文 ...
- nginx+tomcat 分布时服务部署
一. 工具 nginx-1.8.0 apache-tomcat-6.0.33 二. 目标 实现高性能负载均衡的Tomcat集群: 三. 步骤 1.首先下载Nginx,要下载稳定 ...
- git操作github仓库基本操作
一.登录 首次登录 $ git config --global user.name "Your Name" $ git config --global user.email ‘yo ...
- 关于$(function(){})的问题
在开发过程中遇到了一个问题 , 页面需要一个列表展示 , 为了方便数据的获取和渲染 ,就选择了easy UI的网格来做 , 这个时候问题就出现了 , 那就是网格需要触发的函数不写在$(function ...
- java第八次课堂笔记
- 将MD5值压缩成8位32进制生成8位长度的唯一英文数字组合字符串
function str16to32($a){ for($a = md5( $a, true ), $s = '0123456789ABCDEFGHIJKLMNOPQRSTUV', $d = '', ...
- 嵌入式单片机,ATmega328P,外部中断INT0,INT1,INT2,中断标志位介绍
body, table{font-family: 微软雅黑; font-size: 13.5pt} table{border-collapse: collapse; border: solid gra ...