0x00
c# 中的委托和事件一直是记了忘忘了记,反反复复,头大的很……
这次做一个系统的总结吧
0x01 Delegate
委托( delegate ),是一种存储函数引用的类型。声明一个委托与声明函数类似,区别在于不包含函数体,需要使用关键字 delegate。一个委托包含其可以引用的函数签名(返回类型和参数列表)。
可以完成类似传递“函数指针”作为参数的操作,进行回调。在运行之前只有这个变量即可,而不需要知道调用的是哪一个函数的引用。
- 定义一个委托类型
- 声明该委托类型的一个变量
- 将变量初始化为具有相同函数签名(返回值和参数列表)的函数引用
- 使用此委托变量调用它引用的任何函数
对于委托的初始化赋值,可以使用两种语法:
假设我们定义了一个委托变量 process,对应了上面的第一步和第二步。
delegate double ProcessDelegate(double arg1, double arg2);
ProcessDelegate process
我们的使用目的实在特定的打印位置调用一个类似这样的函数,如加减乘除四个方法,这样在该位置只调用 process 引用的方法就可以了,但在此之前,要根据需要对 process 进行初始化。
process = new ProcessDelegate(Multiply) // double Multiply(double arg1, double arg2)
process = Divide; // double Divide(double arg1, double arg2)
这个语法可能有点怪,需要特别记忆:使用 new 关键字创建了一个新的委托,指定了委托的类型,并提供了一个待引用的函数作为参数,这里只需要函数名就可以了。
而后面这种初始化方式,是将工作交给了编译器,编译器会判断委托类型与函数签名是否符合,然后自动地初始化一个委托。不像第一种可以一眼看出是一个委托的初始化。
在实际使用中,也不必经常重复地定义委托类型。c# 中提供了两种内置的委托,Action<arg1, arg2, …> 和 Func<arg1, arg2, …, return> 区别仅仅是是否要返回返回值(最后一个参数作为返回值)。
而 delegate 关键字还有另外一个作用,用作匿名函数,在这种情况下,使用 lambda 表达式也许更方便,但要知道匿名函数的本来面目是很容易混淆的 delegate 啊(其实了解了委托是函数引用也没那么容易混淆了)。
多播( Multicasting of a Delegate )
也称组播,委托对象可使用 “+” 运算符进行合并。一个合并委托调用它所合并的两个委托。只有相同类型的委托可被合并。"-" 运算符可用于从合并的委托中移除组件委托。这样就可以创建一个委托被调用时要调用的方法的调用列表。但是多播委托的调用顺序无法保证。
0x02 Event
事件类似于异常,都由对象引发(或抛出),然后通过提供的代码进行处理。事件需要在处理之前进行订阅( subscribe ),表示提供了在事件发生时需要执行的代码,即事件处理程序。C# 中的事件“event”关键字是语言层面的“观察者模式”的实现,将事件的发起者(被观察者)与处理者(观察者)的代码解耦。
事件处理程序需要被匹配事件所要求的返回值和参数,这个限制由委托来指定。
声明一个事件可以用这样的语句,要记得,Action 是没有返回值的内置委托类型:
public event Action<string, string, object[]> OnEvent;
而订阅的方式,类似这样:
MyClass.OnEvent += new MyDelegate(MyHandleFuncName);
***(?)***我的理解:事件只是在某种情况下,通过委托调用了一开始不知道是啥的函数。事件( event )关键字的作用是增加了订阅(+=)这个操作(并不是,这是委托的多播),让函数引用得以连接到事件对象上。所以事件的触发是以一种( 委托 )函数调用的方式进行的。
所以事件的定义离不来特定的委托类型,一个事件可以通过委托的 Invoke() 方法触发,进而使所有监听了这个事件的处理函数执行。
- 首先要定义一个委托类型
- 通过这个委托类型定义一个事件
- 在类内或外部,使用相同的委托绑定处理函数
- 将处理函数的委托订阅到事件
- 在触发事件时,即可通过委托执行相应处理函数
上面的例子中,使用内部委托,然后进行了第二步,随后将第三步第四步合并到了一行代码中,最后触发事件,使用:
OnEvent(str1, str2, payload);
OnEvent.?Invoke(str1, str2, payload);
使用( .? )避免为 null 时调用报错宕机。
多播可以来处理一个事件发生需要有多个处理程序响应的情况。
而还有一种情况是,要区分相应多个事件的同一个处理程序(称为多用途的事件处理程序)。为由不同对象引发的几个相同事件使用同一个事件处理程序,并且需要指定是由哪个对象生成了事件。
通过内置的委托 EventHandler 和 EventHandler 分别用来处理事件不提供数据和提供数据两种不同的处理方法。其中的类型 T 需要继承自 EventArgs,用来提供事件数据。