C#精要 - 委托与事件篇
委托和事件的区别是什么?
委托本质是一个继承自System.MulticastDelegate的类;而事件是一个类型成员,是对委托的封装,就和属性是对字段的封装一样。
事件
通过event关键词来定义。是对委托的封装。
为什么封装?
为了让委托链的意义,从一个方法执行队列变成一个可订阅、可通知(触发)的中介。
怎么封装的?
随便在类内定义一个public事件,再查看IL代码多帮我们做了什么:
1.生成一个私有的委托字段(没错还是委托链实现的)
2.生成add和remove方法,内部处理是对生成的委托字段进行Delegate.Combine、Delegate.Remove。
怎么设计?
举个例子的话,WPF的画面交互事件用的EventHandler其实就是一个sender加一个args参数,自定义可以如下的:
1 | delegate void MyEventHandler(EventSender sender,MyEventArgs e); |
委托
委托本质是一个继承自System.MulticastDelegate的类,所以像类一样使用即可。
新建一个委托发生了什么?
当你写delegate的时候,编译器就视为你继承了System.MulticastDelegate类,且生成以下几个字段:
1.构造器
它获取2个参数:一个是对象引用,另一个是引用了回调方法的整数。类似于public MyDelegate(Object @object, IntPtr method)
。但是我们new的时候,不需要这么复杂的参数,直接new MyDelegate(MyMethod)
就可以了,编译器会分析。
最后这2个参数会作为MulticastDelegate的2个重要字段!
2.Invoke方法
用于调用。myDelegate(1);
其实编译器内部就会变成myDelegate.Invoke(1);
3.BeginInvoke、EndInvoke方法
用于异步回调。
MulticastDelegate类是什么?
又称多播委托,是所有委托的父类。
它的内部有3个重要字段和1个内部机制来实现委托链。
3个重要字段
字段 | 类型 | 说明 |
---|---|---|
_target | System.Object | 这个字段引用的是回调方法要操作的对象。当委托对象包装静态方法时,这个字段返回null;当委托对象包装实例方法时,这个字段引用回调方法要操作的对象。在委托构造器中将对象引用作为参数获取、赋值给_target。 |
_methodPtr | System.IntPtr | 一个内部的整数值,CLR用它标识要回调的方法。在委托构造器中将回调方法IntPtr作为参数获取、赋值给_methodPtr。 |
_invocationList | System.Object | 只有单个委托时为null。构造委托链时它引用一个委托数组。 |
1个机制
从外部新建一个委托的整个生命流程来理解:
1.申明一个委托实例fbChain,为null。
2.对其进行 += 操作(Delegate.Combine语法糖),Combine方法内部发现fbChain是null,所以直接返回一个_invocationList为null的委托。
3.对其再进行 += 操作,Combine方法内部发现fbChain内部已经包含了一个委托,就会构造一个新的委托对象,这个新委托对象的_invocationList字段引用了一个委托对象数组,里面按顺序包含着之前的所有委托。
4.对委托实例fbChain进行调用,实际就是对整个委托链传入参数,依次遍历进行Invoke。
5.对委托实例fbChain进行 -= 操作(Delegate.Remove语法糖),Remove方法内部倒序循环遍历_invocationList数组,匹配_target和_methodPtr字段相同的元素进行删除,只删除一个元素。
泛型委托
Action和Func
微软定义好的泛型委托就是Action和Func,Action有16个Func有17个。为啥Action少一个?看下:
1 | public delegate void Action();// 这个不是泛型,所以不算,少在这了 |