委托
.NET Framework通过委托来提供回调函数
机制,.NET中的委托类型安全,可以顺序调用,并且支持静态方法和实例方法。
委托示例
// 委托声明
delegate void Callback(int value); // #1
class Program
{
// 静态方法
static void StaticCallbackDemo(int value)
{
Console.WriteLine($"invoke static callback, value: {value}");
}
// 实例方法
void InstanceCallbackDemo(int value)
{
Console.WriteLine($"invoke instance callback, value: {value}");
}
static void Main(string[] args)
{
Callback callback = null;
// 添加静态回调
callback += new Callback(StaticCallbackDemo); // #2
// 添加实例回调
Program program = new Program();
callback += new Callback(program.InstanceCallbackDemo); // #3
// 以下两行代码等效,调用回调
callback(1); // #4
callback.Invoke(1);
Console.ReadLine();
}
}
输出:
invoke static callback, value: 1
invoke instance callback, value: 1
invoke static callback, value: 1
invoke instance callback, value: 1
委托原理
使用ILDasm.exe工具查看程序集,得到如下结果:
在安装Visual Studio的情况下,打开Developer Command Prompt for VS 201*(因版本而异),使用命令ildasm DelegateDemo.exe即可查看程序集
因此,C#编译器在遇到#1处代码时会生成一个类,该类继承自MulticastDelegate类,类中包含构造器,同步的Invoke方法和异步的BeginInvoke方法和EndInvoke方法:
class Callback : MulticastDelegate
{
// 构造器
public Callback(Object object, IntPtr method);
// Invoke方法
public virtual void Invoke(Int32 value);
// BeginInvoke和EndInvoke
public virtual IAsyncResult BeginInvoke(Int32 value, AsyncCallback callback, Object object);
public virtual void EndInvoke(IAsyncResult result);
}
委托的本质是
类
,每个委托对象都是一个包装器,包装了一个方法和调用方法时要操作的对象
MulticastDelegate类和Delegate类
继承关系
System.Object → System.Delegate → System.MulticastDelegate
MulticastDelegate类
MulticastDelegate类有三个重要的非公共字段:
字段名 | 类型 | 说明 |
---|---|---|
_target | System.Object | 指示回调方法要操作的对象的this指针,如果委托对象包装的是静态方法,则为null |
_methodPtr | System.IntPtr | 指示要回调的方法 |
_invocationList | System.Object | 通常为null,构造委托链时引用一个委托数组 |
Delegate类和委托链
#2处代码的+=实际是Delegate类静态方法Combine的重载,所以与以下代码等效:
// 与委托的+=等效
callback = (Callback) Delegate.Combine(callback, new Callback(StaticCallbackDemo));
同样地,-=可以将方法从委托链中删除,它是Delegate.Remove的重载:
// 以下两行代码等效
callback -= new Callback(StaticCallbackDemo)
callback = (Callback) Delegate.Remove(callback, new Callback(StaticCallbackDemo));
Remove按_target和_methodPtr字段查找匹配,并且从后向前查找,找到第一个匹配即停止
当运行到#2处代码时,相当于运行了如下代码:
Callback callback1 = new Callback(StaticCallbackDemo);
callback = (Callback) Delegate.Combine(callback, callback1);
此时callback是null,Combine方法发现要组合的是null和callback1,于是直接返回callback1
当运行到#3处代码时,相当于运行了如下代码:
Program program = new Program();
Callback callback2 = new Callback(program.InstanceCallbackDemo);
callback = (Callback) Delegate.Combine(callback, callback2);
此时callback不是null,Combine方法会构造一个新的委托对象,_invocationList
字段会被初始化为委托对象的数组,数组的第一个元素是callback目前引用的委托,第二个元素是callback2引用的委托,这种可回调多个方法的委托称为委托链
当运行到#4处代码时,该委托发现_invocationList不为null,就执行一个循环来遍历数组中的所有元素,并依次调用每个委托包装的方法
如果委托有返回值,则调用委托链后只返回最后一个委托的结果,如果想得到所有结果,可以使用MulticastDelegate提供的实例方法GetInvocationList
事件
C#中的事件通过委托来实现,对应的委托是System.EventHandler:
public delegate void EventHandler<TEventArgs>(Object sender, TEventArgs e);
所以,对于如下事件定义代码:
// 事件参数
class XXXEventArgs: EventArgs
{
...
}
// 事件所有者
class XXXOwner
{
// 定义事件
public event EventHandler<XXXEventArgs> XXX;
// 触发事件
public void FireXXX(XXXEventArgs e)
{
EventHandler<XXXEventArgs> temp = Volatile.Read(ref XXX);
if(temp != null)
temp(this, e);
}
}
// 事件监听者
class XXXListener
{
public void Listen(Object sender, XXXEventArgs e) // 由泛型System.EventHandler委托类型定义而来
{
...
}
}
编译器会将事件编译成3个构造:
-
一个被初始化为null的私有委托字段
private EventHandler<XXXEventArgs> XXX = null;
即使原始代码将事件定义为public,委托字段也是private
-
一个公共add_XXX方法,调用System.Delegate的静态Combine方法将委托实例添加到委托列表中,返回新的列表头地址并将这个地址存回字段
-
一个公共remove_XXX方法,调用System.Delegate的静态Remove方法将委托实例从委托列表中删除,返回新的列表头地址并将这个地址存回字段
编译器还在程序集的元数据中生成一个
事件定义
的记录项,这个记录项包含一些标志(flag)和基础委托类型(underlying delegate type),还引用了add和remove访问器方法,建立事件的抽象概念和它的访问器方法之间的联系,编译器和其他工具可利用这些元数据信息(通过System.Reflection.EventInfo获取),但是CLR本身不使用这些元数据信息,它在运行时只需要访问器方法
参考
《CLR via C#》(第4版) 第11章,第17章