.NET委托和事件

委托

.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类有三个重要的非公共字段:

字段名类型说明
_targetSystem.Object指示回调方法要操作的对象的this指针,如果委托对象包装的是静态方法,则为null
_methodPtrSystem.IntPtr指示要回调的方法
_invocationListSystem.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个构造:

  1. 一个被初始化为null的私有委托字段

    private EventHandler<XXXEventArgs> XXX = null;
    

    即使原始代码将事件定义为public,委托字段也是private

  2. 一个公共add_XXX方法,调用System.Delegate的静态Combine方法将委托实例添加到委托列表中,返回新的列表头地址并将这个地址存回字段

  3. 一个公共remove_XXX方法,调用System.Delegate的静态Remove方法将委托实例从委托列表中删除,返回新的列表头地址并将这个地址存回字段

编译器还在程序集的元数据中生成一个事件定义的记录项,这个记录项包含一些标志(flag)和基础委托类型(underlying delegate type),还引用了add和remove访问器方法,建立事件的抽象概念和它的访问器方法之间的联系,编译器和其他工具可利用这些元数据信息(通过System.Reflection.EventInfo获取),但是CLR本身不使用这些元数据信息,它在运行时只需要访问器方法

参考

《CLR via C#》(第4版) 第11章,第17章

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值