委托的内部机制
委托是一种定义方法签名的类型,是对方法的抽象、封装。与委托的签名(由返回类型和参数组成)匹配的任何可访问类和结构中的任何方法都可以分配给该委托,方法可以是静态方法,也可以是实例方法。将一个方法绑定到委托时,C#和CLR允许引用类型的协变性和逆变性。
协变性是指方法的返回类型可以派生自委托的返回类型。
逆变性是指委托的参数类型可以派生自方法的参数类型。
协变性和逆变性只能用于引用类型,不能用于值类类型或void。所以不能将下面的方法与委托绑定:
delegate Object DelegateCallBack(FileStream s); public int CallBackMethod(Stream s) { //dosomthing return 1; }
而下面的方法是可以进行绑定的:
public string CallBackMethod(Stream s) { //dosomthing return "1"; }
委托具有以下特点:
1.委托类似于C++函数指针,但它们是类型安全的。
2.委托允许将方法作为参数进行传递。
3.委托可以用于定义回调方法。
4.委托可以链接在一起;例如,可以对一个事件调用多个方法。
5.方法不必与委托签名完全匹配。
通过反编译工具可以知道委托类型会被编译成一个类(一个sealed类,所以不允许从委托类型派生任何类型),并且这个类继承自System.MulticastDelegate类(所有委托类型都派生自该类型)。委托继承了MulticastDelegate类的字段、属性和方法,其中三个重要的对于理解委托及委托链的内部机制非常有帮助的是_target, _methodPtr, _invocationList。具体解释一下:
_target:当委托对象包装一个静态方法时,该值为null。包装实例方法时,该值是回调方法要做操的对象。
_methodPtr:一个内部的整数值,标示要回调的方法。
_invocationList:委托链时,引用一个委托数组,否则为null。
下面通过具体的代码,调试查看上述三个字段的实际赋值情况:
回调静态方法:
public delegate void DelegateSample(string content); static void Main(string[] args) { DelegateSample staticInstance = SendMessage; Console.ReadLine(); } public static void SendMessage(string content) { Console.WriteLine(content); }
调试可以发现:
_target并不是为null,而是引用的方法信息,为什么呢?这个我暂时也不知道。但是Target属性的值为null,没有问题,这个属性分装了字段_target,回调静态方法时为null。
回调实例方法:
static void Main(string[] args) { DelegateSample x = new Program().SendQQ; Console.ReadLine(); } public void SendQQ(string content) { Console.WriteLine(content); }
调试查看委托实例x:
委托链:
稍微修改一下上面的例子,哎,这个例子改的我自己都快受不了了。
static void Main(string[] args) { DelegateSample x = new Program().SendMessage; DelegateSample sample = new DelegateSample(new Program().SendQQ); sample += SendEmial; sample += new Program().SendMessage; DelegateSample s = SendEmial;//与 sample 链中SendEmial同一个对象 sample.Invoke("通知"); new Program().SendNotice("QQ通知:*****", x); Console.ReadLine(); }
调试查看委托实例x:可以发现_invocationList被初始化为引用一个委托数组。
委托实例x:
委托链数组的第三个元素与委托实例x相同,实际上这两个委托都是方法SendMessage的引用,所以截图中的数值相同也就不大惊小怪了,既然是同一个方法的引用,那么函数的入口地址就应该相同。
另外一点:在对委托经行+=操作时,当发现当前委托变量已经引用了委托对象时,会新构造一个委托对象,并对新对象进行初始化,最后设置委托变量引用新的委托对象,之前的委托对象以及_invocationList字段引用的委托数组将会被当做垃圾某个时刻回收,-=操作同样会新建委托对象。
总结:
1.声明委托的本质是声明一个代表着某一系列方法的类。
2.委托链就是一个委托,其内部含有一个委托对象数组。
3.调用委托的本质是调用实例化委托对象中的Invoke方法,委托链则是遍历其委托对象数组,依次调用数组中的方法。