作者:浪子花梦,一个有趣的程序员 ~
事件机制,是一种非常重要的类型,定义了事件成员的类型允许类型通知其他对象发生了特定的事情 . . .
此文章将详细的讲解事件的这些事,文章将分为两块内容进行讲解:
- 事件的完全声明 与 事件的简略声明
- 事件在实际应用中的场景(包含线程安全的讲解)
在我们刚学习到事件的时候,难免会想到以前接触过的 Windows 消息机制,消息相关文章如下所示:
Win32消息处理机制与窗口制作
那么,事件与消息到底有什么区别呢?
解释如下:
事件是一个动作——用户触发的动作。
消息是一个信息——传递给系统的信息。
事件bai与消息的概念在计算机中较易混淆,但本质不同:
事件由用户(操作电脑的人)触发且只能由用户触发,操作系统能够感觉到由用户触发的事件,并将此事件转换为一个(特定的)消息发送到程序的消息队列中
。
可以说“用户触发了一个事件”,而不能说“用户触发了一个消息”。
对于事件的真正定义是什么,我这里也不去讲究了,下面让我们来认识 C# 中的事件是如何使用的 . . .
在实际的编码中,如果没有什么特殊的情况,我们一般都使用事件的简略声明,但是我们得需要了解简略声明的背后到底干了什么事情吧,这样才能使我们写代码的过程中,看的更清楚一点 . . .
而且我们必须知道 事件的本质就是将委托给封装了起来
,之所以有这种操作,就是使我们更加了方便,但是如此我们不了解这些东西,就会变的非常迷 . . .
实现一个完美事件,我们需要知道下面几个概念:
如果我们不记得如此写出一个事件,那么我们只需要记住上面这 5点概念就行了,将他一个一个的实现出来,一个完整的事件机制就写好了 . . . 下面让我们接触一下事件是什么样子的呢 . . .
事件的完全声明比较简略声明多了一点东西,但是我们知道事件的完全声明,来真正的了解了事件定义的整个过程,首先,我们需要一个 Student 学生的类,这个学生在 调用一个方法的时候,会调用一个事件来说明我是谁的功能,实现如下所示:
在上面我们可以看出来,对一个事件订阅或者取消订阅就是调用事件的 add、remove方法对委托的一个操作而已,这个上面我们已经知道了:
还有二个概念我们没有接触,下面我们将实现这二个功能
这两个功能就是: 1. 事件的订阅 . 2. 事件处理器
事件的简略声明非常的简单,我们只需要将 Student中数据改变一下就行了,如下所示:
我们发现一个很好玩的事情,就是委托字段不见了,还有 add、remove方法也不见了 . . .
这是因为语法糖的效果,把他们都给隐藏了起来,我们打开 ILDASM 反编译工具查看如下所示:
箭头所指的就是委托字段,框起来的就是两个方法 add 与 remove,这些东西都隐藏在程序的背后,所以,我们在用简略声明事件的时候,一定要清楚程序的背后发生了什么事情,这样我们才能做一个优秀的 C# 程序员 ~ 大家有空可以研究一下 ILDASM 反编译工具,这是 VS 自带的一个工具,用起来也比较方便,大家可以慢慢的研究 . . .
上面的例子中我对 委托的命名使用错误了,不应该是 EventArgs结尾 ,EventArgs 表示的是事件的参数,应该以 Handler结尾比较好,大家注意一点
,我们主要讲解事件的完全声明与简略声明的区别,下面我们演示一个例子,这个例子中的语法格式也是我们实际中经常操作的部分,如下所示:
指向的委托是 C#环境中自带的泛型委托,它接收各种各样的事件参数,如上所示,事件是参数是一个叫 SayHelloEventArgs 的类型,它派生自 EventArgs,订阅事件处理器的情况如下所示:
结果如下所示:
此例参考《CLR via C#》
我们需要设计一个 MailManager 的类型来接收传入的电子邮件,它公开一个事件名称为 NewMail,其它类型 Fax 对该事件的关注,比较简单,如下所示 . . .
1)定义一个事件参数类型 NewMailEventArgs(存放着一些信息,无所谓的数据):
2) 定义事件:
我们可以查看一下 EventHandler 委托的定义,如下所示:
他的类型接收两个参数,一个是事件的拥有者,还有一个就是事件的参数了,也就是上面我们定义的事件参数类型 NewMailEventArgs 了. . .
.
3)定义一个调用事件的方法:
这里面包含线程安全的情况,我们下面在继续讨论 . . .
.
4)定义方法,将输入的数据封装调用上面的调用事件方法:
5) 定义一个类Fax(传真机),用于处理事件的订阅与事件处理器:
6. 测试这个邮件系统代码的事件:
在上面的代码中,我们有一个地方考虑到了线程安全,如下所示:
如果我们没有考虑线程安全的话,那么原本的代码应该是如下的样子:
那么我们为什么要像第一种方式那样书写呢?虽然我们检查出 NewMail 不为 null,但在调用 NewMail之前,另一个线程可能会从委托链中移除一个委托,使 NewMail 成了 null,这就会引发空引用的异常 . . .
对 Volatile.Read 的调用强迫 NewMail 在这个调用发生时读取,引用真的必须复制到 temp 变量中,使用这个技术可以完美的解决这个担忧,这个线程安全的技术本人还不是太了解,以后再说吧 . . .