一个委托可以绑定多个方法,使用"+="就可以向委托中添加新的方法,使用"-="可以从委托中删除方法:
如果我们从委托中减去一个它根本未添加过的方法,会怎么样呢?答案是不会报错,也不会有任何影响。
还有更加现实的问题,就是如果添加的方法中有多个方法都有返回值,那怎么办?只返回最后一个具有返回值的方法的返回值。
委托的工作原理
我们先来个委托类型的声明:
public delegate void Show();
就是这样简单的一句声明,在编译器那边可是一个大事件:
public class Show : System.MulticastDelegate
{
//构造器
public Show(Object object, IntPtr method);
//与声明一样的方法
public virtual void Invoke();
....
}
编译器会生成一个与委托同名的类,该类有四个方法,这里只关注前两个,剩下两个方法涉及到异步调用,暂且不谈。
所有的委托都有构造器,该构造器接受两个参数:对象引用object和方法标识值method(整数)。对象引用其实就是this引用,而方法标识值是一个特殊的整数,它标识了我们想要回调的方法,它是从MethodDef或MemberRef元数据token获得,至于这两个东西到底是什么,这里不展开,因为我只是初学者,最好不要一开始就纠缠于编译器实现的内容。对于静态方法,object为null,因为静态方法没有this引用。
这两个值是理解委托工作原理最重要的部分。
我们先看看它们保存在哪里。在编译器生成的类中,有三个继承自MulticastDelegate的私有字段:
_target: 对象引用;
_methodPtr:标识要回调的方法的整数值;
_invocationList:构造方法组时,引用一个委托数组。
_invocationList这个字段非常特殊,一般都是null,但如果我们为委托添加一个方法组(method group, 有些地方称为委托链),该字段就会引用一个委托数组。在我们使用"+="为委托添加方法的时候,其实就是添加方法组。什么叫方法组呢?就是能够被委托实例包装的方法,它们的特点就是匹配委托声明的签名和返回值。当我们添加方法组的时候,编译器其实都在为我们添加的每一个方法生成一个委托,然后将该委托和前一个委托放进一个数组中,该数组的大小刚刚好能够容纳这两个委托。就是因为这样,使得每次添加新的方法(委托)时,编译器都必须重新创建一个新的数组来容纳新的委托,然后将之前的委托和数组都交给垃圾回收器回收。其实从_invocationList中的List就可以知道它的低层是怎样实现的。学过java的人一定对容器类List非常熟悉,它的工作原理也是同样的道理:低层是一个数组,当该数组无法容纳新的元素时,就会自动将数组大小扩为两倍。
从方法组中删除方法也是同样的过程,如果方法组中还有超过一个非null的元素,就会生成一个新的数组来容纳剩下的元素。但删除方法只能一次删除一个,而不是删除所有匹配的方法,事实上方法组中的所有方法都是匹配的。