C# Event (1) —— 我想搞个事件
本文地址: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的时候会想尽一切办法通知你(包括但不限于),而你只需要等着接收通知即可。
闹钟负责发布消息,而你关注着闹钟发来的消息,这就构成了一对“发布者-订阅者”的关系(你订阅了闹钟发布的事件)。
publisher[发布者]
subscriber[订阅者]
publisher -->|发布事件| subscriber -->|订阅通知| publisher
subscriber -->|采取行动| subscriber
alarm[闹钟]
you[你]
alarm -->|8点啦| you -->|听到| alarm
you -->|起床| you
关于发布者和订阅者这一名称,可以想象一家报纸,在发生某个重大事件时,这家报纸把这个事件刊载到头版分发出去,所有订阅这家报纸的人就都能获取到这一消息。
C#的Event
现在我们可以用C#来表示上面我们提到的这个关系。
假设一个人是这样的:
public partial class Person
{
private bool isSleeping;
public bool IsSleeping{get => isSleeping;}
public Person()
{
isSleeping = true;
Console.WriteLine("Sleeping...");
}
public void GetUp()
{
isSleeping = false;
Console.WriteLine("Wake up!");
}
}
So far so good,这个Person
将作为订阅者,但是这个人一旦睡着,除非他自己主动想GetUp
以外没有任何可以醒来的方式。
于是,我们把发布者(也就是闹钟)引入进来,需要说明的是,我们这里并不打算做一个可以真的会走的闹钟,只是做一个简单的闹钟模型来模拟一下经过。
public partial class Alarm
{
private int hour;
public int Hour
{
get => hour;
private set
{
hour = value % 24;
Console.WriteLine($"It's {hour} o'clock.");
}
}
public Alarm(int hour)
{
this.Hour = hour;
}
public void StepHour() //往前走1小时
{
Hour++;
}
}
现在两个角色都建立起来了,但是没有建立事件的联系,也就是说现在的闹钟和你之间各玩各的,谁也管不着谁。
声明一个事件
C#中使用event
关键字来声明一个事件……
……
那事件本身如何处理呢??实际上C#中事件处理器本质上是一个特殊的委托。
我们知道,委托是可执行的方法集,就像一把枪,装上若干可执行的函数作为子弹,然后在调用委托的时候把子弹全部打出去(里面的方法挨个调用一遍),当然了,和子弹不同,方法打出去之后并不会消失,而是还留在委托内(术语上称之为多路广播机制)。C#事件就是使用委托这套机制来实现的。
在这个例子中我们姑且先定义一个无参数的委托:
public void delegate Time8Handler();
然后在Alarm
中加上:
public partial class Alarm
{
public event Time8Handler Time8;
}
这样就在Alarm侧声明了一个名为Time8
事件,需要注意这句声明,public event Time8Handler Time8;
如果拿走event
关键字,那么这句话就是一个普通的委托声明,但具备event
之后它就变成了事件委托。
事件委托的特点
事件委托和普通委托有什么区别呢。在事件发布类的范围内,事件和一个委托没有什么区别,但在事件外,事件有如下特点:
对于发布器以外的对象,不能使用
()
或Invoke
主动调用事件委托(实际上是只能出现在+=
和-=
之左侧)
了解了上述特点之后我们就能更进一步理解C#中的发布-订阅机制是如何运作的:
发布者提供一个事件委托作为事件入口,比如
Alarm.Time8
,发布者的事件允许订阅者将自己的事件行为(处理函数)装入这个事件。
订阅者只决定了自身在事件中采取的行动,而发布者则只决定事件产生/行动执行的时机(何时调用)。
在发布者调用事件的这一刻,事件内所有被装入的行为方法会被一炮打出,订阅者就会执行相应的行为。
发布者(Publisher) - Alarm
于是在Alarm
中,我们需要确定执行的Time8
的执行时机,由于我们希望这个事件在hour
达到8的时候执行,由于这里我们使用属性Hour
做了封装了,因此我们直接在setter里以发布者的名义调用该事件委托,setter里面做如下修改
public partial class Alarm
{
//...
public int Hour
{
// getter ...
private set
{
hour = value % 24;
Console.WriteLine($"It's {hour} o'clock.");
if(hour == 8)
Time8?.Invoke(); // <<--1
}
}
//...
}
注意<<--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
函数:
public partial class Person
{
public void OnTime8()
{
GetUp();
}
}
到目前为止我们只是定义了打算用于事件的函数,但这个函数本身还没有和Time8
事件建立联系。
接下来我们有两种做法来完成这件事,一是在Main
中声明Alarm
和Person
各一个实例,然后手动+=
来进行实际的订阅,像这样:
static void Main(string[] args)
{
Person p = new Person();
Alarm a = new Alarm(6);
a.Time8 += p.OnTime8; // <<--2
// ...
}
<<--2
处就完成了p
对a.Time8
事件的订阅。如果此刻我们继续往下写……
static void Main(string[] args)
{
// ...
a.StepHour(); // a变成了7点,没到8点不会起床
a.StepHour(); // a变成了8点,执行Time8委托,其中包含了p.OnTime8
}
执行之后的结果就是
Sleeping...
It's 6 o'clock.
It's 7 o'clock.
It's 8 o'clock.
Wake up!
还有一种做法,就是我在Person
里声明一个Alarm
然后让Person
订阅自己Alarm
的Time8
事件,这么写语义上更能体现这个闹钟是我的闹钟的含义,这种写法我会在下面代码汇总中提供。
总结
这一部分我们初步了解了事件在C#中表现的角色和作用,通过一个十分简单的例子体验了一下事件的基本用法,在后续的几篇中我们会遇到更加复杂的情形,敬请期待……
代码汇总
public delegate void Time8Handler();
public class Alarm
{
private int hour;
public int Hour
{
get => hour;
private set
{
hour = value % 24;
Console.WriteLine($"It's {hour} o'clock.");
if (hour == 8)
Time8?.Invoke();
}
}
public Alarm(int hour)
{
this.Hour = hour;
}
public void StepHour()
{
Hour++;
}
public event Time8Handler Time8;
}
public class Person
{
public Alarm alarm;
private bool isSleeping;
public bool IsSleeping { get => isSleeping; }
public Person(int hour)
{
isSleeping = true;
Console.WriteLine("Sleeping...");
alarm = new Alarm(hour);
alarm.Time8 += OnTime8;
}
public void GetUp()
{
isSleeping = false;
Console.WriteLine("Wake up!");
}
public void OnTime8()
{
GetUp();
}
}
public class Program
{
static void Main(string[] args)
{
Person p = new Person(6);
p.alarm.StepHour();
p.alarm.StepHour();
}
}
C# Event (1) —— 我想搞个事件的更多相关文章
- Event Delivery: The Responder Chain(事件传递,响应链)
当我们设计app的时候,我们很可能想动态的响应事件.例如,触摸一个拥有许多不同对象的屏幕,你要决定给哪个对象一个响应事件,怎么样对象接收到事件. 当一个用户产生事件发生时(如 点击),UIKit产生一 ...
- ng-change事件中如何获取$event和如何在子元素事件中阻止调用父级元素事件(阻止事件冒泡)
闲聊: 今天小颖要实现一个当改变了select内容后弹出一个弹框,并且点击select父元素使得弹框消失,这就得用到阻止事件的冒泡了:$event.stopPropagation(),然而小颖发现,在 ...
- 想学设计模式、想搞架构设计,先学学UML系统建模吧您
UML系统建模 1 概述 1.1 课程概述 汇集UML及其相关的一些话题 回顾UML相关的符号与概念 以电商订单相关业务为例,借助UML完成系统建模 将UML变成提升建模效率,表达架构思想的工具 1. ...
- 之前想搞一个nim但因为是自用我会持续修复完善
异步方式的优点:客户端和服务端互相解耦,双方可以不产生依赖.缺点是:由于引入了消息中间件,在编程的时候会增加难度系数.此外,消息中间件的可靠性.容错性.健壮性往往成为这类架构的决定性因素. 幸运的是程 ...
- event.preventDefault和恢复元素默认事件
写页面事件的时候,有的时候需要用event.preventDefault取消原有的事件后进行重写,这个大家应该都知道. 那么怎么在取消默认事件后再恢复呢. 解绑我们自定义的事件就好了. 以Jquery ...
- ORA-00017: session requested to set trace event 请求会话以设置跟踪事件
ORA-00017: session requested to set trace event ORA-00017: 请求会话以设置跟踪事件 Cause: The current se ...
- 就想搞明白,component-scan 是怎么把Bean都注册到Spring容器的!
作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 忒复杂,没等搞明白大促都过去了! 你经历过618和双11吗?你加入过大促时候那么多复 ...
- YisouSpider你想搞死我的服务器吗?
在1分钟666次请求中,你占了445次,你大爷的想干啥呢? 42.156.254.30 - - [03/Feb/2016:11:46:00 +0800] "GET /thread-22063 ...
- python 想搞加密算法吗?快戳这里
加密算法介绍 一,HASH Hash,一般翻译做"散列",也有直接音译为"哈希"的,就是把任意长度的输入(又叫做预映射,pre-image),通过散列算法,变换 ...
随机推荐
- 初步认识微前端(single-spa 和 qiankun)
初步认识微前端 微前端是什么 现在的前端应用,功能.交互日益复杂,若只由一个团队负责,随着时间的推进,会越来越庞大,愈发难以维护. 微前端这个名词,第一次提出是在2016年底.它将微服务(将单一应用程 ...
- ApacheCN Kali Linux 译文集 20211020 更新
Kali Linux 秘籍 中文版 第一章 安装和启动Kali 第二章 定制 Kali Linux 第三章 高级测试环境 第四章 信息收集 第五章 漏洞评估 第六章 漏洞利用 第七章 权限提升 第八章 ...
- CF954H Path Counting
一开始的想法是枚举路径的 \(\rm LCA\) 然后再枚举两边的深度,但是这样无论如何我都只能做到 \(O(n ^ 3)\) 的复杂度. 只能考虑换一种方式计数,注意到点分治可以解决树上一类路径问题 ...
- Java-在数组中遍历出最值
在操作数组时,经常需要获取数组中元素的最值. 代码 public class Example31{ public static void main(String[] args){ int[] arr= ...
- Sublime Text4 安装与配置记录
Sublime Text作为一款优质的Code编辑器,已更新至第4个版本,本文记录关于Sublime Text 4[版本4126]的安装.汉化,以及常用配置方法. 安装 访问官网下载安装包:https ...
- java中args是什么意思?
1. 字符串变量名(args)属于引用变量,名字代号而已,可以自己取的. 2.总的来说就是个存放字符串数组用的, 去掉就不知道 "args" 声明的变量是什么类型了. 3.如果有 public sta ...
- Swift字符串的介绍
字符串的介绍 字符串在任何的开发中使用都是非常频繁的 OC和Swift中字符串的区别 在OC中字符串类型时NSString,在Swift中字符串类型是String OC中字符串@"" ...
- Docker创建私有镜像仓库
Docker官方提供了一个工具docker-registry,可以借助这个工具构建私有镜像仓库: 1.拉取registry镜像 # docker pull registry//可以使用 docker ...
- Spring中声明式事务处理和编程式事务处理的区别
编程式事务:所谓编程式事务指的是通过编码方式实现事务,即类似于JDBC编程实现事务管理.管理使用TransactionTemplate或者直接使用底层的PlatformTransactionManag ...
- 面试官:谈谈你对IO流和NIO的理解
一.概念 NIO即New IO,这个库是在JDK1.4中才引入的.NIO和IO有相同的作用和目的,但实现方式不同,NIO主要用到的是块,所以NIO的效率要比IO高很多.在Java API中提供了两套N ...