回调函数
回调函数是一种有用的编程机制,就是通过函数指针调用的函数。C#里通过委托来提供回调函数的机制。不同于C/C++,委托确保回调方法是类型安全的,支持静态方法和类实例的成员方法,还允许顺序调用多个方法。
委托的内部构造
编译器实际上会为委托定义一个完整的类。类包含:
- 4个方法:一个构造器、Invoke函数、BeginInvoke和EndInvoke。
- 三个主要的字段 _target、 _methodPtr、 _invocationList
字段 | 类型 | 说明 |
---|---|---|
_target | System.Object | 当委托对象包装一个静态方法时,这个字段为null。当委托包装一个实例方法时,这个字段引用的是回调方法要操作的对象。换言之,这个字段指出要给实例方法的隐式参数this的值。 |
_methodPtr | System.IntPtr | 一个内部的IntPtr整数值,CLR用它标识要回调的方法。 |
_invocationList | System.Object | 该字段通常为null。构造委托链时它引用一个委托数组。 |
可以看到fb1、fb2都是委托包装了静态方法,fb3是委托包装了实例方法。Invoke被调用时,如果是成员函数,委托会使用使用私有字段_target和_methodPtr在指定对象上调用包装好的回调方法。
委托链
如果委托链中只有一个委托fb1那么内部的成员变量如下。如果委托链中有多个委托,那么成员变量如下。在fbChain引用的委托上调用Invoke时,该委托发现私有字段_invocationList不为null,所以会执行一个循环来遍历数组中的所有元素,并依次调用每个委托包装的方法。
委托链的局限性,除了最后一个委托中的返回值,其他回调方法的返回值都会被丢弃。如果调用的委托中有一个抛出了异常或者阻塞了相当长时间,链中后续的所有对象都调用不了。
注意事项
- 一个类型中通过委托带来调用另一个类型的私有成员,只委托对象是由具有足够安全性/可访问性的代码创建的,便没有问题。
- 用lambda表达式进行匿名函数委托,实际上会生成一个类。并将使用到的局部变量传递到这个类的字段内。这实际延长了引用对象的生命周期。在大多数应用程序中不是大问题。但有时要注意下。
参考
- 《CLR via C#(第四版)》