第 20、21、22节 事件详解
第20、21、22节 事件详解、Linq 详解
- 初步了解事件
- 事件的应用
- 事件的声明
- 事件与委托的关系
初步了解事件
事件的功能 = 通知 + 可选的事件参数(即详细信息)
定义:单词 Event,译为“事件”
1)《牛津词典》中的解释是“a thing that happens, especially something important”
2)通顺的解释就是“能够发生的什么事情”角色:使对象或类具备通知能力的成员
1)(中译)事件(event)是一种使对象或类能够提供通知的成员
2)(原文)An event is a member that enables an object or class to provide notifications.
3)“对象O拥有一个事件E”想表达的思想是:当事件E发生的时候,O有能力通知别的对象使用:用于对象或类间的动作协调与信息传递(消息推送)
原理:事件模型(event model)中的两个“5”
1)“发生–>响应”中的5个部分——闹钟响了你起床、孩子饿了你做饭……这里隐含着“订阅”关系
2)“发生–>响应”中的5个动作——(1)我有一个事件–>(2)一个人或者一群人关心我的这个事件–>(3)我的这个事件发生了–>(4)关心这个事件的人或被依次通知到(根据订阅的次序)–>(5)被通知到的人根据拿到的事件信息(又称“事件数据”、“事件参数”、“通知”)对事件进行响应(又称“处理事件”)。提示
1)事件多用于桌面、手机等开发的客户端编程,因为这些程序经常是用户通过事件来“驱动”的
2)各种编程语言对这个机制的实现方法不尽相同
3)Java 语言里面没有事件这种成员,也没有委托这种数据类型。Java 的“事件”是使用接口来实现的
4)MVC、MVP、MVVM 等模式,是事件模式更高级、更有效的“玩法”
5)日常开发的时候,使用已有事件的机会比较多,自己声明事件的机会比较少,所以先学使用
常用术语:
(1)事件的订阅者、事件消息的接收者、事件的响应者、事件的处理者、被事件所通知的对象
(2)事件信息、事件消息、事件数据、事件参数
事件的应用
实例演示
派生(继承)与扩展(extends)事件模型的五个组成部分
1)事件的拥有者(event source,对象)
2)事件成员(event,成员)
3)事件的响应者(event subscriber,对象)
4)事件的处理器(event handler,成员)——本质上是一个回调方法
5)事件订阅——把事件处理器与事件关联在一起,本质上是一种委托类型为基础的“约定”
(1)标准的事件机制模型,MVC、MVP等设计模式的雏形(☆)
(2)事件的拥有者是事件响应者的一个字段成员(☆☆☆)
(3)
(4)用自己的方法订阅自己的事件(☆☆)
举例:
(1)一个事件多个处理器(using System.Timers; )
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Timers; //Timernamespace EventExample
{class Program{static void Main(string[] args){ //事件的拥有者 timerTimer timer = new Timer();//Interval 时间间隔的长短//单位:mstimer.Interval = 1000;Boy boy = new Boy();Girl girl = new Girl();//一个事件两个事件处理器timer.Elapsed += boy.Action;timer.Elapsed += girl.Action;timer.Start();Console.ReadLine();}}class Boy{internal void Action(object sender, ElapsedEventArgs e){Console.WriteLine("Jump!");}}class Girl{internal void Action(object sender, ElapsedEventArgs e){Console.WriteLine("Sing!");}}
}
(2)标准的事件机制模型 ☆(using System.Windows.Forms;)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms; //Form 类namespace EventExample
{class Program{static void Main(string[] args){//事件的拥有者 formForm form = new Form();//事件的响应者 controllerController controller = new Controller(form);form.ShowDialog();}}class Controller{private Form form;public Controller(Form form){if (form != null){this.form = form;//+= 用于挂接事件处理器//事件 Click//事件订阅this.form.Click += this.FormClicked;}}//事件处理器 FormClicked()private void FormClicked(object sender, EventArgs e){this.form.Text = DateTime.Now.ToString();}}
}
(3)事件的拥有者和事件的响应者是同一个对象 ☆☆
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;namespace EventExample
{class Program{static void Main(string[] args){//事件的拥有者 form MyForm form = new MyForm();//事件 Click//事件的响应者 form//事件的订阅form.Click += form.FormClicked;form.ShowDialog();}}//MyForm 继承自 Formclass MyForm : Form{//事件处理器 FormClicked()internal void FormClicked(object sender, EventArgs e){this.Text = DateTime.Now.ToString();}}
}
(4)事件拥有者是事件响应者的一个字段成员 (最常用)☆☆☆
//事件的拥有者 myButton
//事件 Click
//事件的响应者 myTextBox
//事件订阅 this.myButton.Click += this.ButtonClicked;(Form1.Designer.cs)
namespace FormApp
{public partial class MyForm : Form{public MyForm(){InitializeComponent();}//事件处理器private void ButtonClicked(object sender, EventArgs e){this.myTextBox.Text = "Hello, world!";}}
}
(5)一个事件可以挂接多个事件处理器,一个事件处理器也可以被多个事件所挂接
//一个事件处理器可以被重用,
//前提是该事件处理器必须和被处理事件保持约束上的一致
namespace WinFormExample
{public partial class Form1 : Form{public Form1(){InitializeComponent();//利用委托的挂接方式this.button3.Click += new EventHandler(this.ButtonClicked);//直接将事件挂接在 ButtonClicked()方法上this.button3.Click += this.ButtonClicked;//利用匿名方法作为事件处理器,已经过时的挂接方法this.button3.Click += delegate (object sender, EventArgs e){this.textBox1.Text = "Haha!";};//使用lamda表达式(=>)作为事件处理器this.button3.Click += (sender, e) => { this.textBox1.Text = "Hohoho!";};}private void ButtonClicked(object sender, EventArgs e){if (sender == this.button1){this.textBox1.Text = "Hello!";}if (sender == this.button2){this.textBox1.Text = "World!";}if (sender == this.button3){this.textBox1.Text = "Sunnie!";}}}
}
- 注意
1)事件处理器是方法成员
2)挂接事件处理器的时候,可以使用委托实例,也可以直接使用方法名,这是个“语法糖”
3)事件处理器对事件的订阅不是随意的,匹配与否由声明事件时所使用的委托类型来检测
4)事件可以同步调用也可以异步调用
事件的声明
- 事件的声明
1)完整声明
2)简略声明(字段式声明,field-like)
举例:
(1)完整声明
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Threading;//委托是事件的底层基础,事件是委托的上层建筑
namespace EventExample
{class Program{static void Main(string[] args){Customer customer = new Customer();Waiter waiter = new Waiter();customer.Order += waiter.Action;customer.Action();customer.PayTheBill();}}//用来传递事件信息的class,所以class的名字为(事件名+EventArgs)public class OrderEventArgs : EventArgs{public string DishName { get; set; }public string Size { get; set; }}//事件是基于委托的//1.事件需要使用委托类型做约束 // (约束:规定了事件能够发送什么样的消息给事件的响应者,// 也规定了事件的响应者能收到什么样的消息,// 所以就要求事件的响应者要与约束匹配上;)//2.能够记录或者引用方法的任务只有委托实例才能做到。//如果委托是为了声明某个事件而准备的,则委托的名字以 EventHandler 为后缀。public delegate void OrderEventHandler(Customer customer, OrderEventArgs e);public class Customer{private OrderEventHandler orderEventHandler;//声明事件的关键字 eventpublic event OrderEventHandler Order {add{this.orderEventHandler += value;}remove{this.orderEventHandler -= value;}}public double Bill { get; set; }public void PayTheBill(){Console.WriteLine("I will pay ${0}.", this.Bill);}public void WalkIn(){Console.WriteLine("Walk into the restaurant.");}public void SitDown(){Console.WriteLine("Sit Down.");}public void Think(){for (int i = 0; i < 5; i++){Console.WriteLine("Let me think ...");Thread.Sleep(1000);}if (this.orderEventHandler != null){OrderEventArgs e = new OrderEventArgs();e.DishName = "Kongpao Chicken";e.Size = "large";this.orderEventHandler.Invoke(this, e);}}public void Action(){Console.ReadLine();this.WalkIn();this.SitDown();this.Think();}}public class Waiter{public void Action(Customer customer, OrderEventArgs e){Console.WriteLine("I will serve you this dish - {0}.",e.DishName);double price = 10;switch (e.Size){case "small":price = price * 0.5;break;case "large":price = price * 1.5;break;default:break;}customer.Bill += price;}}
}
(2)简略声明
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Threading;namespace EventExample
{class Program{static void Main(string[] args){Customer customer = new Customer();Waiter waiter = new Waiter();customer.Order += waiter.Action;customer.Action();customer.PayTheBill();}}//用来传递事件信息的class,所以class的名字为(事件名+EventArgs)public class OrderEventArgs : EventArgs{public string DishName { get; set; }public string Size { get; set; }}public class Customer{public event EventHandler Order;public double Bill { get; set; }public void PayTheBill(){Console.WriteLine("I will pay ${0}.", this.Bill);}public void WalkIn(){Console.WriteLine("Walk into the restaurant.");}public void SitDown(){Console.WriteLine("Sit Down.");}public void Think(){for (int i = 0; i < 5; i++){Console.WriteLine("Let me think ...");Thread.Sleep(1000);}this.OnOrder("Kongpao Chicken", "large");}// 访问级别为 protected ,不能为 public//触发事件的方法,一般命名为 On + 事件名(例如:OnOrder)protected void OnOrder(string dishName,string size){if (this.Order != null){OrderEventArgs e = new OrderEventArgs();e.DishName = dishName;e.Size = size;this.Order.Invoke(this, e);}}public void Action(){Console.ReadLine();this.WalkIn();this.SitDown();this.Think();}}public class Waiter{public void Action(object sender, EventArgs e){Customer customer = sender as Customer;OrderEventArgs orderInfo = e as OrderEventArgs;Console.WriteLine("I will serve you this dish - {0}.", orderInfo.DishName);double price = 10;switch (orderInfo.Size){case "small":price = price * 0.5;break;case "large":price = price * 1.5;break;default:break;}customer.Bill += price;}}
}
有了委托字段/属性,为什么还需要事件?
为了程序的逻辑更加“有道理”,更加安全,谨防“借刀杀人”所以事件的本质是委托字段的一个包装器
1)这个包装器对委托字段的访问起限制作用,相当于一个“蒙板”
2)封装(encapsulation)的一个重要功能就是隐藏
3)事件对外界隐藏了委托实例的大部分功能,仅暴露添加/移除事件处理器的功能
4)添加/移除事件处理器的时候可以直接使用方法名,这是委托实例所不具备的功能用于声明事件的委托类型的命名约定
1)用于声明 Foo 事件的委托,一般命名为 FooEvenHandler (除非是一个非常通用的事件约束)
2)FooEventHandler 委托的参数一般有两个(由Win32 API 演化而来,历史悠久)
(1)第一个是object类型,名字为sender,实际上就是事件的拥有者、事件的source。
(2)第二个是EventArgs类的派生类,类名一般为 FooEventArgs,参数名为e。也就是事件参数
(3)虽然没有官方说法,但我们可以把委托的参数列表看做是事件发生后发送给事件响应者的“事件消息”。
3)触发Foo事件的方法一般命名为OnFoo,即“因何引发”、“事出有因”
(1)访问级别为protected,不能为public,不然又成了可以“借刀杀人”了事件的命名约定
1)带有时态的动词或者动词短语
2)事件拥有者“正在做”什么事情,用进行时;事件拥有者“做完了”什么事情,用完成时。
事件与委托的关系
事件真的是“以特殊方式声明的委托字段/实例”吗?
1)不是!只是声明的时候“看起来像”(对比委托字段与事件的简化声明,field-like)
2)事件声明的时候使用了委托类型,简化声明造成事件看上去像一个委托的字段(实例),而 event 关键字则更像是一个修饰符——这就是错觉的来源之一
3)订阅事件的时候 += 操作符后面可以是一个委托实例,这与委托实例的赋值方法语法相同,这也让事件看起来像是一个委托字段——这是错觉的又一来源
4)重申:事件的本质是加装在委托字段上的一个“蒙板”(mask),是一个起掩蔽作用的包装器。这个用于阻挡非法操作的“蒙板”绝不是委托字段本身为什么要使用委托类型来声明事件?
1)站在 source 的角度来看,是为了表明 source 能对外传递哪些消息
2)站在 subscriber 的角度来看,它是一种约定,是为了约束能够使用什么样签名的方法来处理(响应)事件
3)委托类型的实例将用于存储(引用)事件处理器对比事件与属性
1)属性不是字段——很多时候属性是字段的包装器,这个包装器用来保护字段不被滥用
2)事件不是委托字段——它是委托字段的包装器,这个包装器用来保护委托字段不被滥用
3)包装器永远不可能是被包装的东西
第 20、21、22节 事件详解相关推荐
- 2021-06-20-刘铁猛C#语言入门详解-学习笔记P20、21、22事件详解
P20.21.22事件详解 一.P20.21.22内容总结 事件的概念P20 事件的应用P21:四个实例 事件的声明P22 问题辨析P22:事件与委托的关系 二.事件的概念P20 事件的角色:使对象或 ...
- python代码案例详解-第7.20节 案例详解:Python抽象类之真实子类
第7.20节 案例详解:Python抽象类之真实子类 上节介绍了Python抽象基类相关概念,并介绍了抽象基类实现真实子类的步骤和语法,本节结合一个案例进一步详细介绍. 一. 案例说明 本节定义了图形 ...
- 移动端开发touchstart,touchmove,touchend事件详解和项目
移动端开发touchstart,touchmove,touchend事件详解和项目 最近在做移动端的开发,在一个"服务商管理"页面使用到了触摸事件"touchstart& ...
- android 拖动 点击事件,Android事件详解——拖放事件DragEvent
1.Android拖放框架的作用? 利用Android的拖放框架,可以让用户用拖放手势把一个View中的数据移到当前layout内的另一个View中去. 2.拖放框架的内容? 1)拖放事件类 2)拖放 ...
- oracle 10046事件详解
10046事件详解 一.10046事件概述 10046是一个Oracle的内部事件(event),通过设置这个事件可以得到Oracle内部执行系统解析.调用.等待.绑定变量等详细的trace信息,即帮 ...
- Cesium 事件详解(鼠标事件、相机事件、键盘事件、场景触发事件)
Cesium 事件详解(鼠标事件.相机事件.键盘事件.场景触发事件) 1 Cesium中的事件 根据使用情况,我把Cesium中的事件大体分为三种,即屏幕空间事件处理程序,屏幕空间相机控制器,场景触发 ...
- 【小白学PyTorch】扩展之Tensorflow2.0 | 21 Keras的API详解(下)池化、Normalization
<<小白学PyTorch>> 扩展之Tensorflow2.0 | 21 Keras的API详解(上)卷积.激活.初始化.正则 扩展之Tensorflow2.0 | 20 TF ...
- solidity事件详解
很多同学对Solidity 中的Event有疑问,这篇文章就来详细的看看Solidity 中Event到底有什么用? 写在前面 Solidity 是以太坊智能合约编程语言,阅读本文前,你应该对以太坊. ...
- MySQL Installer 8.0.21安装教程图文详解 转载
MySQL Installer 8.0.21安装教程图文详解 原地址 1. 缘由 刚好需要在新系统上重新安装MySQL,便写了一份的下载安装教程,供查阅,以防日后细节有所遗忘. 2. 版本说明 MyS ...
最新文章
- 谷歌AI错杀Chrome插件,全职奶爸程序员“睡后收入”被迫终结
- WebRequest 请求被中止: 请求已被取消。 错误解决方法
- java开发cs项目_本硕机械转行cs(java后端开发)上岸之路
- android viewpager 底部tabhost,FragmentTabHost+ViewPager实现底部导航栏
- 200820C阶段一通用链表
- leetcode 853. Car Fleet | 853. 车队(Golang)
- cassandra 环境搭建
- android动画框架,GitHub - azhengyongqin/CustomAnimationFramework: Android自定义曲线路径动画框架...
- log4cplus c++开源日志系统
- 同时处理知网、万方、维普数据库——CiteSpace、Ucinet、Vosviewer等
- STM32串口通信DMA方式
- 计算机英语 1000字论文范文,英语论文格式写作 1000字论文格式-免费论文范文
- C语言图形库——easyx的使用
- 5分钟商学院-个人篇-高效能人士的思维习惯
- Kotlin-字符串小写转大写
- python utf 8 mac_Mac python 开发环境一些设置
- html下拉框背景怎么设透明度,css怎么设置背景图片半透明 css设置图片作为背景的透明度...
- bin是什么文件,要如何打开?
- Java学习日记(一)
- 1-20的两个数把和告诉A,积告诉B,A说不知道是多少,B也说不知道,这时A说我知道了,B说我也知道了,请你猜猜这两个数的和是多少
热门文章
- 我心目中的Top Ten 之 运动篇
- 上交计算机专硕学费3万,学费从4万涨到12万?上海交大这个专业“太贵”,家长:供不起...
- 阿迪达斯健身跟踪器:具备音乐播放列表和跑步路线推荐功能
- Quartus如何生成顶层文件里的小模块,解决波形图无法导入输入输出
- protobuf gzip压缩 解压缩的使用方法
- scrapy学习笔记
- 二分查找你确定真的会?生活中还能用来设计骗局?
- 老宇哥带你玩转ESP32:03 GPIO数字输入与数字输出
- #记录一下:关于三维建图的一些文章讲解 + Delaunay三角剖分的含义
- 再生龙 (Clonezilla)。比 Ghost更棒的、免费的、中文接口的 硬盘备份与还原