委托和事件的区别是什么?

委托本质是一个继承自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
2
3
4
5
6
7
8
9
public delegate void Action();// 这个不是泛型,所以不算,少在这了
public delegate void Action<T>(T obj);// 1个参数的
...
public delegate void Action<T1, ..., T16>(T1 arg1, ..., T16 arg16);// 最多16个参数的

public delegate TResult Func<TResult>();
public delegate TResult Func<T, TResult>(T obj);// 1个参数的
...
public delegate TResult Func<T1, ..., T16, TResult>(T1 arg1, ..., T16 arg16);// 最多16个参数的