本文地址:https://www.cnblogs.com/oberon-zjt0806/p/15975299.html

本文最初来自于博客园

本文遵循CC BY-NC-SA 4.0协议,转载请注明出处

本文还会有后续,所以本篇只是一个非常简单的场景

Event(事件)

何谓Event,我们不妨看一下词典对Event的解释:

something that happens, especially something important, interesting or unusual.

在发生的某件事,特指比较重要、有趣或者非同寻常的事情

- Longman Dictionary

历史上或社会上发生的不平常的大事情。

- 现代汉语词典

可能会有人觉得,这说了跟没说一样,其实不然,无论是中文还是英文的解释,都侧重于一点——

不寻常,值得关注

而我们的目的也在于此,我们需要关注程序内部可能发生的一些特定的状况,然后在这个时间点下采取行动,这个特定的发生状况的时间点就是我们需要关注的事件。

当然,词典可能把事件这个此上升到了社会这种高度,认为特别重大的才叫做事件。但在程序中,其实任何需要关注的事情我们都可以冠以事件的名头。

比如??

你每天早上9点上班,但是你家到目的地需要差不多1个小时,那么8点钟到了(OnTime8)这件事就可以成为你需要关注的事件

如果你不希望因为迟到导致自己遭受惩罚的时候,那么你就必须保证在8点钟到了的时候采取正确的行动——起床(GetUp)。

换言之,你不能错过这个事件。

那我如何不错过这个事件??

当然,作为一个顶级大忙人,你可不会在这个事件发生之前一直盯着钟看,不然你可能就没有心思做别的事情了,毕竟在8点之前你还有别的事情要忙,比如……忙着睡觉

在这种情况下,如果你不想因为睡过头而错过这一事件的时候(而你自己又没办法一直盯着钟发呆)的情况下,就需要有别的东西(或者人,如果可以的话)帮你盯着时间,然后在适当的时机告知你。

比如,一台性能强劲的闹钟(强劲到无论你睡多死都能把你叫醒),就是一个不错的选择。

发布者-订阅者(Publisher-Subscriber)

好了,现在你发现你和闹钟之间构成了这样一种关系:

闹钟(当然和世界时间保持一致)负责盯着时间,在观察到指针从7到达8的时候会想尽一切办法通知你(包括但不限于),而你只需要等着接收通知即可。

闹钟负责发布消息,而你关注着闹钟发来的消息,这就构成了一对“发布者-订阅者”的关系(你订阅了闹钟发布的事件)。

graph LR
publisher[发布者]
subscriber[订阅者]

publisher -->|发布事件| subscriber -->|订阅通知| publisher
subscriber -->|采取行动| subscriber

alarm[闹钟]
you[你]

alarm -->|8点啦| you -->|听到| alarm
you -->|起床| you

关于发布者和订阅者这一名称,可以想象一家报纸,在发生某个重大事件时,这家报纸把这个事件刊载到头版分发出去,所有订阅这家报纸的人就都能获取到这一消息。

C#的Event

现在我们可以用C#来表示上面我们提到的这个关系。

假设一个人是这样的:

  1. public partial class Person
  2. {
  3. private bool isSleeping;
  4. public bool IsSleeping{get => isSleeping;}
  5. public Person()
  6. {
  7. isSleeping = true;
  8. Console.WriteLine("Sleeping...");
  9. }
  10. public void GetUp()
  11. {
  12. isSleeping = false;
  13. Console.WriteLine("Wake up!");
  14. }
  15. }

So far so good,这个Person将作为订阅者,但是这个人一旦睡着,除非他自己主动想GetUp以外没有任何可以醒来的方式。

于是,我们把发布者(也就是闹钟)引入进来,需要说明的是,我们这里并不打算做一个可以真的会走的闹钟,只是做一个简单的闹钟模型来模拟一下经过。

  1. public partial class Alarm
  2. {
  3. private int hour;
  4. public int Hour
  5. {
  6. get => hour;
  7. private set
  8. {
  9. hour = value % 24;
  10. Console.WriteLine($"It's {hour} o'clock.");
  11. }
  12. }
  13. public Alarm(int hour)
  14. {
  15. this.Hour = hour;
  16. }
  17. public void StepHour() //往前走1小时
  18. {
  19. Hour++;
  20. }
  21. }

现在两个角色都建立起来了,但是没有建立事件的联系,也就是说现在的闹钟和你之间各玩各的,谁也管不着谁。

声明一个事件

C#中使用event关键字来声明一个事件……

……

那事件本身如何处理呢??实际上C#中事件处理器本质上是一个特殊的委托

我们知道,委托是可执行的方法集,就像一把枪,装上若干可执行的函数作为子弹,然后在调用委托的时候把子弹全部打出去(里面的方法挨个调用一遍),当然了,和子弹不同,方法打出去之后并不会消失,而是还留在委托内(术语上称之为多路广播机制)。C#事件就是使用委托这套机制来实现的。

在这个例子中我们姑且先定义一个无参数的委托:

  1. public void delegate Time8Handler();

然后在Alarm中加上:

  1. public partial class Alarm
  2. {
  3. public event Time8Handler Time8;
  4. }

这样就在Alarm侧声明了一个名为Time8事件,需要注意这句声明,public event Time8Handler Time8;如果拿走event关键字,那么这句话就是一个普通的委托声明,但具备event之后它就变成了事件委托。

事件委托的特点

事件委托和普通委托有什么区别呢。在事件发布类的范围内,事件和一个委托没有什么区别,但在事件外,事件有如下特点:

对于发布器以外的对象,不能使用()Invoke主动调用事件委托(实际上是只能出现在+=-=之左侧)

了解了上述特点之后我们就能更进一步理解C#中的发布-订阅机制是如何运作的:

发布者提供一个事件委托作为事件入口,比如Alarm.Time8,发布者的事件允许订阅者将自己的事件行为(处理函数)装入这个事件。

订阅者决定了自身在事件中采取的行动,而发布者则只决定事件产生/行动执行的时机(何时调用)。

在发布者调用事件的这一刻,事件内所有被装入的行为方法会被一炮打出,订阅者就会执行相应的行为。

发布者(Publisher) - Alarm

于是在Alarm中,我们需要确定执行的Time8的执行时机,由于我们希望这个事件在hour达到8的时候执行,由于这里我们使用属性Hour做了封装了,因此我们直接在setter里以发布者的名义调用该事件委托,setter里面做如下修改

  1. public partial class Alarm
  2. {
  3. //...
  4. public int Hour
  5. {
  6. // getter ...
  7. private set
  8. {
  9. hour = value % 24;
  10. Console.WriteLine($"It's {hour} o'clock.");
  11. if(hour == 8)
  12. Time8?.Invoke(); // <<--1
  13. }
  14. }
  15. //...
  16. }

注意<<--1处标记的代码,语法上这里实际上可以直接用Time8()调用。但是我们不能排除Time8委托里没有装入任何方法以及Time8 == null的情形,在这种情况下直接调用事件委托会抛出System.NullReferenceException异常。

因此MSDN中推荐使用?.Invoke()的方式调用可以轻松避免这种问题,当然如果不喜欢这样做的话那就谨记在Time8();之前用if(Time8 != null)来判断一下。

至此发布者的角色准备就绪。

订阅者(Subscriber) - 你

好了,现在闹钟可以响了,但是如果你不在意闹钟的声音的话,那闹钟就算把自己敲烂也不会叫醒你,毕竟常言说得好:

你永远叫不醒一个装睡的人

因此只有你关注(订阅)了发布者的事件时,你才会受到事件的影响。

首先我们考虑当Alarm.Time8事件发生时,作为Person的你会采取什么行为,显然,在本例中,你需要GetUp

不过注意一下,尽管在本例中你可以直接把public void GetUp()订阅到Time8上(因为委托型相同),但这并非一个很好的做法,在更加复杂的情形下,我们可能需要GetUp同时还要执行别的事情(比如Wash或者Breakfast什么的),种种原因可能会使你不能把这些行为都订阅到这个事件上(1是委托型不保证相同,2是难以保证在多路广播下委托装入的函数执行顺序是什么样的),因此更明智的做法是单独为事件委托提供一个事件函数,然后让这个事件函数单独订阅专门的事件,所以我们这里在定义一个OnTime8函数:

  1. public partial class Person
  2. {
  3. public void OnTime8()
  4. {
  5. GetUp();
  6. }
  7. }

到目前为止我们只是定义了打算用于事件的函数,但这个函数本身还没有和Time8事件建立联系。

接下来我们有两种做法来完成这件事,一是在Main中声明AlarmPerson各一个实例,然后手动+=来进行实际的订阅,像这样:

  1. static void Main(string[] args)
  2. {
  3. Person p = new Person();
  4. Alarm a = new Alarm(6);
  5. a.Time8 += p.OnTime8; // <<--2
  6. // ...
  7. }

<<--2处就完成了pa.Time8事件的订阅。如果此刻我们继续往下写……

  1. static void Main(string[] args)
  2. {
  3. // ...
  4. a.StepHour(); // a变成了7点,没到8点不会起床
  5. a.StepHour(); // a变成了8点,执行Time8委托,其中包含了p.OnTime8
  6. }

执行之后的结果就是

  1. Sleeping...
  2. It's 6 o'clock.
  3. It's 7 o'clock.
  4. It's 8 o'clock.
  5. Wake up!

还有一种做法,就是我在Person里声明一个Alarm然后让Person订阅自己AlarmTime8事件,这么写语义上更能体现这个闹钟是我的闹钟的含义,这种写法我会在下面代码汇总中提供。

总结

这一部分我们初步了解了事件在C#中表现的角色和作用,通过一个十分简单的例子体验了一下事件的基本用法,在后续的几篇中我们会遇到更加复杂的情形,敬请期待……

代码汇总

  1. public delegate void Time8Handler();
  2. public class Alarm
  3. {
  4. private int hour;
  5. public int Hour
  6. {
  7. get => hour;
  8. private set
  9. {
  10. hour = value % 24;
  11. Console.WriteLine($"It's {hour} o'clock.");
  12. if (hour == 8)
  13. Time8?.Invoke();
  14. }
  15. }
  16. public Alarm(int hour)
  17. {
  18. this.Hour = hour;
  19. }
  20. public void StepHour()
  21. {
  22. Hour++;
  23. }
  24. public event Time8Handler Time8;
  25. }
  26. public class Person
  27. {
  28. public Alarm alarm;
  29. private bool isSleeping;
  30. public bool IsSleeping { get => isSleeping; }
  31. public Person(int hour)
  32. {
  33. isSleeping = true;
  34. Console.WriteLine("Sleeping...");
  35. alarm = new Alarm(hour);
  36. alarm.Time8 += OnTime8;
  37. }
  38. public void GetUp()
  39. {
  40. isSleeping = false;
  41. Console.WriteLine("Wake up!");
  42. }
  43. public void OnTime8()
  44. {
  45. GetUp();
  46. }
  47. }
  48. public class Program
  49. {
  50. static void Main(string[] args)
  51. {
  52. Person p = new Person(6);
  53. p.alarm.StepHour();
  54. p.alarm.StepHour();
  55. }
  56. }

C# Event (1) —— 我想搞个事件的更多相关文章

  1. Event Delivery: The Responder Chain(事件传递,响应链)

    当我们设计app的时候,我们很可能想动态的响应事件.例如,触摸一个拥有许多不同对象的屏幕,你要决定给哪个对象一个响应事件,怎么样对象接收到事件. 当一个用户产生事件发生时(如 点击),UIKit产生一 ...

  2. ng-change事件中如何获取$event和如何在子元素事件中阻止调用父级元素事件(阻止事件冒泡)

    闲聊: 今天小颖要实现一个当改变了select内容后弹出一个弹框,并且点击select父元素使得弹框消失,这就得用到阻止事件的冒泡了:$event.stopPropagation(),然而小颖发现,在 ...

  3. 想学设计模式、想搞架构设计,先学学UML系统建模吧您

    UML系统建模 1 概述 1.1 课程概述 汇集UML及其相关的一些话题 回顾UML相关的符号与概念 以电商订单相关业务为例,借助UML完成系统建模 将UML变成提升建模效率,表达架构思想的工具 1. ...

  4. 之前想搞一个nim但因为是自用我会持续修复完善

    异步方式的优点:客户端和服务端互相解耦,双方可以不产生依赖.缺点是:由于引入了消息中间件,在编程的时候会增加难度系数.此外,消息中间件的可靠性.容错性.健壮性往往成为这类架构的决定性因素. 幸运的是程 ...

  5. event.preventDefault和恢复元素默认事件

    写页面事件的时候,有的时候需要用event.preventDefault取消原有的事件后进行重写,这个大家应该都知道. 那么怎么在取消默认事件后再恢复呢. 解绑我们自定义的事件就好了. 以Jquery ...

  6. ORA-00017: session requested to set trace event 请求会话以设置跟踪事件

    ORA-00017: session requested to set trace event   ORA-00017: 请求会话以设置跟踪事件 Cause:       The current se ...

  7. 就想搞明白,component-scan 是怎么把Bean都注册到Spring容器的!

    作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 忒复杂,没等搞明白大促都过去了! 你经历过618和双11吗?你加入过大促时候那么多复 ...

  8. YisouSpider你想搞死我的服务器吗?

    在1分钟666次请求中,你占了445次,你大爷的想干啥呢? 42.156.254.30 - - [03/Feb/2016:11:46:00 +0800] "GET /thread-22063 ...

  9. python 想搞加密算法吗?快戳这里

    加密算法介绍 一,HASH Hash,一般翻译做"散列",也有直接音译为"哈希"的,就是把任意长度的输入(又叫做预映射,pre-image),通过散列算法,变换 ...

随机推荐

  1. 初步认识微前端(single-spa 和 qiankun)

    初步认识微前端 微前端是什么 现在的前端应用,功能.交互日益复杂,若只由一个团队负责,随着时间的推进,会越来越庞大,愈发难以维护. 微前端这个名词,第一次提出是在2016年底.它将微服务(将单一应用程 ...

  2. ApacheCN Kali Linux 译文集 20211020 更新

    Kali Linux 秘籍 中文版 第一章 安装和启动Kali 第二章 定制 Kali Linux 第三章 高级测试环境 第四章 信息收集 第五章 漏洞评估 第六章 漏洞利用 第七章 权限提升 第八章 ...

  3. CF954H Path Counting

    一开始的想法是枚举路径的 \(\rm LCA\) 然后再枚举两边的深度,但是这样无论如何我都只能做到 \(O(n ^ 3)\) 的复杂度. 只能考虑换一种方式计数,注意到点分治可以解决树上一类路径问题 ...

  4. Java-在数组中遍历出最值

    在操作数组时,经常需要获取数组中元素的最值. 代码 public class Example31{ public static void main(String[] args){ int[] arr= ...

  5. Sublime Text4 安装与配置记录

    Sublime Text作为一款优质的Code编辑器,已更新至第4个版本,本文记录关于Sublime Text 4[版本4126]的安装.汉化,以及常用配置方法. 安装 访问官网下载安装包:https ...

  6. java中args是什么意思?

    1. 字符串变量名(args)属于引用变量,名字代号而已,可以自己取的. 2.总的来说就是个存放字符串数组用的, 去掉就不知道 "args" 声明的变量是什么类型了. 3.如果有 public sta ...

  7. Swift字符串的介绍

    字符串的介绍 字符串在任何的开发中使用都是非常频繁的 OC和Swift中字符串的区别 在OC中字符串类型时NSString,在Swift中字符串类型是String OC中字符串@"" ...

  8. Docker创建私有镜像仓库

    Docker官方提供了一个工具docker-registry,可以借助这个工具构建私有镜像仓库: 1.拉取registry镜像 # docker pull registry//可以使用 docker ...

  9. Spring中声明式事务处理和编程式事务处理的区别

    编程式事务:所谓编程式事务指的是通过编码方式实现事务,即类似于JDBC编程实现事务管理.管理使用TransactionTemplate或者直接使用底层的PlatformTransactionManag ...

  10. 面试官:谈谈你对IO流和NIO的理解

    一.概念 NIO即New IO,这个库是在JDK1.4中才引入的.NIO和IO有相同的作用和目的,但实现方式不同,NIO主要用到的是块,所以NIO的效率要比IO高很多.在Java API中提供了两套N ...