一.委托的使用
在delphi中回调机制是通过函数指针来实现的,也就是通过函数的地址实现的并没有参数的个数、类型,返回值类型等信息,总而言之是非类型安全的。在.NET中回调机制是通过委托来实现的。委托是一种类型安全的回调机制,并支持多路传播。
委托的使用(单播委托):
1.声明委托 public delegate void CallBack(int MyArg);
我们可以看到委托CallBack代表的有一个int型参数,无返回值型的函数,这样就确保了被调用的方法的签名是正确的,因此它是类型安全的。
2.实例化委托 CallBack MyCallBack = new CallBack(MyClass.FeedBack);
3.调用委托 MyCallBack(3);
多播委托:
上面的委托只包含一个方法,如果要调用多个方法的话,只能显式多次调用该委托。委托也可以调用多个方法,这就叫多播委托。
delegate 提供了静态方法Combine方法向委托链添加委托,静态方法Remove移除委托。为了减轻开发人员的编程工作,C#编译器自动为委托类型实例重载了 += 和 -=操作符。C#编译器将这两个操作符在内部翻译为Combine和Remove方法(这个可以用ILDasm.exe工具验证)。
使用多播委托时,若委托对象有返回值的话只会返回最后一个回调方法的返回值,其他的返回值都会被抛弃。而且,如果被调用的委托有一个被诸塞了很久或抛出了异常呢?由于委托链上的对象是按顺序依次调用的,所以委托链上的一个委托对象除了问题会阻碍委托链上其他委托对象的执行。
为了解决这种算法不适合的情况,MulticastDelegate类提供了GetInvocationList()方法,我们可以显式的调用委托链上的各个委托对象,这样就可以根据需要来设计算法调用委托链上的委托对象。
二.解密委托
委托的使用似乎非常简单,但是事情并不像上面的例子那样简单。编译器和CLR在后台做了很多工作来隐藏问题本身的复杂性。下面介绍编译器和CLR是如何实现委托的。
仍以public delegate void CallBack(int MyArg)为例:
编译器自动生成了一个完整的类定义:
public sealed class CallBack : System.MulticastDelegate
{
public CallBack(object <urn:object:5> @object, IntPtr <urn:object:6> methodPtr);
public hidebysig virtual instance void Invoke(int32 ‘value’);
public virtual IasyncResult BeginInvoke(int32 value, CallBack callback, Object object);
public virtual void <urn:object:5> EndInvoke(IAsyncResult <urn:object:6> result);
}
因为所有的委托都继承自System.MulticastDelegate,自然也就包含了System.MulticastDelegate中的字段。下面列出了几个比较重要的字段:
1. private object <urn:object:95> _target; 指向回调函数被调用应该被调用的对象。
2.private IntPtr <urn:object:93> _methodPtr; 一个内部的整数值,用来标识回调函数。
3.private MulticastDelegate <urn:object:37> _prev; 指向另一个委托。(通过该字段可形成委托链)
所有的委托都有构造函数,并且该构造函数接收两个参数:一个对象引用,一个指向回调方法的内部Int值。看我们例子中传入的参数是MyClass.FeedBack,并没有传入对象引用和指向回调方法的内部Int值,之所以不抱错是因为编译器回通过分析源码来确定我们引用的是那个对象和方法。其中对象引用传递给参数target,指向回调方法的内部Int值传递给参数methodPtr。对于静态方法,null会被传给target。在构造函数内部,这两个参数分别被传递给_target和_methodPtr。
每个委托对象实际是对方法及其调用方法时操作的对象的封装。MulticastDelegate定义了两个属性:Target和 Method.给定一个委托对象的引用,我们可以察看这些属性。
下面我们看看回调方法是怎样被调用的。看上面的例子, 我们似乎调用了一个MyCallBack函数,并传递了一个参数。但是实际上并没有MyCallBack函数。因为编译器知道MyCallBack是一个指向委托对象的变量,所以它会自动添加代码来调用该委托对象的Invoke方法。
至此,我们对委托的用法就不难理解了