1、概念
回调函数是一种非常有用的编程机制,但是在在非托管的C/C++中,非成员函数的地址只是一个内存地址,不带任何额外的信息,因此非托管的回调函数是非类型安全的。而在.net下,提供了一种类型安全的回调机制:委托。委托可以回调静态方法、实例方法等。
2、结构
编译器和CLR实现委托是非常复杂的。它需要在幕后做很多工作。
源代码:interna delegate void Feedback(int32 value);
编译器编译结果:
interna class Feedback:System.MulticastDelegate
{
//构造器
public Feedback(Object object, IntPtr method);
//方法原型和源代码指定相同
public virtual void Invoke(Int32 value);
//允许异步调用的方法
public virtual IAsyncResult BeginInvoke(Int32 value,AsnycCallback, Object object);
public virtual void EndInvoke(IAsncResult result);
}
编译器定义的类有4个方法:一个构造器、Invoke、BeginInvoke、EndInvoke。在这里,编译器生成类一个类Feedback,该类继承自MulticastDelegate(所有的委托都继承自该类)。System.MulticastDelegate继承自System.Delegate。在一些特殊情况下,我们会用到Delegate类。
因为委托是一个类,所以可以在定义类的地方定义委托。
所有的委托类型都继承自MulticastDelegate类,该类有3个非公共字段非常重要。
_target(System.Object):当委托封装的是一个静态方法时,该字段为null,当委托对象封装一个实例方法时,该字段引用的是调用回调方法时要操作的对象。即这个字段指明要传给实例方法的隐式this的值。
_methodPtr(System.IntPtr):一个内部的整数值,CLR用它来标识要回调的方法
_invocationList(System.Object):该字段通常为null。在构造一个委托链时,它用来引用一个委托数组,委托链引用该数组的最后一个委托。
所有的委托都有一个构造器,该函数有两个参数:一个对象引用和一个引用回调方法的整数。当定义委托时,因为C#编译器知道创建的是委托,他会对源代码进行解析约定引用的是那个对象和方法。对象引用被传递给object参数,标识方法的特殊IntPtr值别传给method参数。对于静态方法,null被传递给object参数。
Delegate类定义了两个只读属性:Target和Method。给定一个对象引用,我们就可以查询这些属性。
3、委托链
链式委托是指有一些列委托对象组成的集合。它允许调用集合中各个委托所表示的所有方法。
Delegate类的静态方法Combine用于添加一个委托到委托链。当调用该方法时,将会构造一个新的委托对象,该对象的_invocationList字段会被初始化为引用一个委托对象数组。该数组的最后一个索引会指向新添加的委托。同时该方法的返回值也会设置为引用新建的委托对象。
Delegate类的静态方法Remove用于从委托链删除委托。
C#编译器为委托类型提供了运算符+=和-=重载,这些运算符分别调用Delegate.Combine和Delegate.Remove。使用这些运算符简化了创建委托链的过程。
MulticastDelegate类提供了一个实例方法:GetInvocationList,用来显示调用链中的每一个委托。
4、C#编译器的便利
C#编译器为委托提供了一些简便的方法,如匿名方法等。
5、委托和反射
为了使用委托,开发人员需要知道回调的方法的原型。但是有时候开发人员在编译时不知道这些信息。System.Delegate提供了几个方法,在编译时不知道委托的这些必要信息是,来创建并调用一个委托。
CreateDelegate方法:创建委托。其中的MethodInfo参数最好用反射API来获取。如果是封装的实例方法,firstArgument指出要作为this参数传给这个实例方法的对象。
DynamicInvoke:调用委托对象的回调方法。它传递一组在运行时才知道的参数。