韩志强
(赤峰学院 计算机科学与技术系,内蒙古 赤峰 0 2 4 0 0 0)
对C#委托内部机制的探析
韩志强
(赤峰学院 计算机科学与技术系,内蒙古 赤峰 0 2 4 0 0 0)
委托是用来处理其它语言(如C/C++、Pascal等)需要用函数指针来处理的情况的.不过与C/C++函数指针不同,委托是完全面对对象的;另外,C/C++指针仅指向成员函数,而委托同时封装了对象实例和方法.
委托;回调函数;多路广播委托;委托推断
对于开发人员来说,在进行程序开发时经常使用回调函数,它是个非常强大的编程特性.在C/C++中程序员利用函数指针,将可执行的步骤作为参数传给另一个方法来实现回调.而C#使用委托来提供相同的功能,委托将方法作为对象封装起来,允许在运行时间接地绑定一个方法调用.下文将对C#委托的内部机制进行探析.
委托是一种引用方法的类型.一旦为委托分配了方法,委托将与该方法具有完全相同的行为.委托方法的使用可以像其他任何方法一样,具有参数和返回值.
在C#中,委托允许将方法作为参数进行传递,可用于定义回调方法,而且委托还可以将方法链接在一起.委托类似于C/C++函数指针,但它是类型安全的.在C/C++中,函数指针其实就是个内存地址,由于该地址不会携带任何其他信息,如函数期望的参数个数、参数类型、返回值类型等,所以这时的回调函数是非类型安全的.C#为回调函数提供了称为委托的机制,其能提供所期望的参数个数、参数类型、返回值类型等信息,因此委托是类型安全的.
委托在C#中被看作一种新的数据类型,由它声明的实例可以引用一个或多个方法.在C/C++中的函数指针只能引用静态函数,而C#中的委托既可以引用静态方法,也可以引用实例方法.在引用实例方法时,委托不仅存储了一个对该方法入口点的一个引用,还存储了一个对相应的对象实例的引用,该方法就是通过此对象实例被调用的.
C#通过以下三个步骤实现一个委托(d e l eg a t e):
(1)声明一个delegate对象,它应与你想要传递的方法具有相同的参数和返回值类型.
(2)创建delegate对象,并将你想要传递的函数作为参数传入.
(3)在要实现异步调用的地方,通过上一步创建的对象来调用方法.
声明一个委托类型的唯一方法是通过使用Delegate关键字进行委托声明.在C#中委托类型的是间接从S y s t e m.Delegate派生的类类型,委托类型隐含为s e a l e d,所以不允许从一个委托类型派生任何类型.也不允许从S y s t e m.Delegate派生非委托类类型.在此要注意的是,S y s t e m.Delegate本身不是委托类型;它是从中派生所有委托类型的类类型.
C#的编译器也不允许程序员用户自己定义一个直接或间接(通过S y s t e m.Multicast Delegate类)从S y s t e m.Delegate派生的类.但C#要求程序人员使用Delegate关键字定义委托,该关键字会使C#编译器生成一个类(即一个委托数据类型).
Delegate关键字是派生自S y s t e m.Multicast D e le g a t e类的一个类(或称数据类型)的别名,派生的委托类包含I n v o k e()方法,该方法可实现委托内方法的调用.S y s t e m.Multicast Delegate类则是从System.Delegate类派生的.其中S y s t e m.Delegate类由一个对象引用和一个方法指针(是S y s t e m.R e f l e ct i o n.M e t h o d i n f o类型的方法指针)构成.创建委托时,编译器自动使用S y s t e m.Multicast Delegate类而不是S y s t e m.Delegate类.S y s t e m.Multicast Delegate类也包含了一个对象引用和一个方法指针(和它的D e l eg a t e基类是一样的),但除此之外,它还包含对另一个S y s t e m.Multicast Delegate对象的引用.
向一个委托添加一个方法时,Multicast Delegate类将会创建委托类型的一个新实例,在新实例中为新增的方法存储对象引用和方法指针,并在委托实例方法列表中添加新的方法作为下一项.这样的结果就是,Multicast Delegate类维护着由多个方法构成的一个链表.该链表列出一个或多个方法,其中每个方法均作为一个可调用实体来引用.对于实例方法,可调用实体由该方法和一个相关联的实例组成.对于静态方法,可调用实体仅由一个方法组成.调用委托时,链表中的方法会被依次调用.委托实例还有个有用的特性是:它不知道也不关心它所封装的方法所属的类;它所关心的仅限于这些方法必须与委托的类型兼容.这使委托非常适合于“匿名”调用.
3.2 委托的声明
委托修饰符可选delegate 返回类型 标识符(形参表可选);
表1 委托声明语法说明
在上例中使用如下语句声明一个委托类型.
publi cdelegate void my delegate(s trngname);
在编译时,C#编译器会生成一个派生自System.Multicast Delegate类的一个名为 my delegate委托类.而后可以使用该类进行委托的实例化.
3.3 委托的实例化
可以使用新实例初始化一个委托实例: my d e le g a t e my d e l e=new my delegate( my d e l e_1);
也可使用委托推断来实现委托的实例化:M ydelegate my d e l e= my d e l e_1;
在编译上述两种情况时,C#编译器创建的代码是一样的.
3.4 调用委托
在上例中, my d e l e("h z q")等效于 my dele.Invoke("h z q")
实际上,给委托实例提供括号与调用委托类的I n v o k e()方法完全相同.在C#中I n v o k e()方法不允许被程序员用户调用. my d e l e是委托类型的一个变量,所以C#编译器会用 my dele.Invoke("h z q")代替 my d e l e("h z q").
由于委托是支持多播的,因此委托除了支持“=”赋值运算符(可初始化一个委托,或将原委托清除,用新的委托替换它们)外,还支持“+/+=”运算符(将其它委托添加到委托链中),及支持“-/-=”运算符(将其它委托从委托链中删除).
在这里应注意的是,无论“+“、“-“还是它们的复合版本(“+=“、“-=“),在内部都是使用静态方法S y s t e m.Delegate.Combine()和S y s t e m.Delegate.Remove()来实现的.这两个方法都获取Delegate类型的两个参数.第一个方法Combine()会连接两个参数,将两个委托的调用列表顺序连接起来.第二个方法Remove()则搜索由第一个参数指定的委托链,并删除由第二个参数指定的委托.对于Combine()方法,它的两个参数都可以为n u l l,其中一个参数为n u l l,Combine()会返回非空的那个参数.如果两个都为n u l l,则 Combine()返回空.
委托在.N E T F r a m e work中应用的非常广泛,同时也是C#编程中的重要元素,如何更好的理解委托,在基于Wi n d o w s平台下的面向对象编程中是非常关键的.为此我在本文中对委托的内部机制进行针对性探析,希望能对在理解委托方面有困难的人能有所帮助.
〔1〕[美]Mark Michaelis.周靖译.C# 本质论.
〔2〕[美]Mark Michaelis.周靖译.C# 本质论(第 2版).
〔3〕[美]Andrew Troelsen. 朱晔等译.C# 与.NET3.5高级程序设计(第4版).
T P 3 1 2
A
1673-260X(2010)10-0037-02