一、事件的基本概念

事件是什么呢?他是一个东西,得能够发生,发生了就会给其他的东西通知。

事件是类的成员。它使类或对象具有通知别的类或对象的能力。

而当然,不仅仅有通知。

有些事件,例如说手机响铃,会伴随着其他的消息,比如谁谁谁领导通知你开会,家人跟你聊天什么的。这种伴随着事件传达的信息称为事件参数

手机响铃会有很多种可能,而有些事件,其发生与通知本身就蕴含了一些信息,没有必要再给你其他的事件参数。比如红灯停绿灯行。

因而,事件的功能=通知+可选的事件参数。它用与类或对象之间的动作协调与信息传递。

/*感觉在C语言中,这个功能是通过使用flag啊isXX等等标志的布尔值或者对数值的判断功能来实现的?*/

事件通知到的那个东西,同时也是接受事件通知、关心事件通知、行动,的那个东西,叫作事件的订阅者。它们接收到事件的信息后采取行动称为响应事件/处理事件。它们处理事件时所进行的行动称为事件处理器

将现实中的具体实物抽象化为事件,我们具有一个“事件模型”的原理。它由两个“5”组成:

其一,发生-->响应的五个部分:如闹钟响了我起床,不只有“闹钟”、“响了”、“我”、“起床”这五个元素,它还包含了“我”关心着“闹钟”这个订阅关系。

其二,发生-->响应的五个动作:(1)我有一个事件(2)有一个人或一群人关心着我这个事件(3)我发生了(4)我依次通知他们并且说明该做的事情(5)他们根据事件参数处理事件

注:

为了防止事件模型逻辑混乱的场面,对于事件的逻辑设置,人类总结出了MVP.MVC、MVVM等设计模式,是事件模式更高级有效的方法。

在日常生活中,声明事件机会较少,更多是使用已有的事件,故更重要学会怎么用。

二、事件的应用

1.事件模型的五个组成部分

(1)事件的拥有者/事件的source(event source,对象)

手机响铃的手机

(2)事件成员(event)

事件本身。

它使类或对象具有通知别的类或对象的能力。

是被动的工具

只有在事件的拥有者完成内部逻辑后触发才能发挥通知的作用

(3)事件的订阅者(event subscriber,对象)

被通知到的类或对象

(4)事件处理器(event handler,成员)————本质上是一个回调方法

(5)事件订阅——把事件处理器与事件关联在一起,本质上是一种以委托类型为基础的约定

解决了三个问题:

有谁被通知到,拿什么样的事件处理器处理这个事件,事件的响应者具体拿哪个方法处理这个事件

C#规定,用于订阅事件的事件处理器必须与事件遵守同一个约定,约定约束了事件能够发送什么样的消息给处理器,也约束处理器能够处理什么样的消息。

2.常用的事件订阅者和拥有者的组合

第一种,事件的拥有者和事件的订阅者处在两个不同的类。这是最基础的一个组合。

第二种,事件的响应者和拥有者是同一个对象。

第三种是最广泛的,事件的拥有者是事件的响应者的一个字段成员

比如说WPF的button。button是事件的拥有者,窗口是事件的响应者,button是窗口的一个字段成员。

3.使用事件

namespace ConsoleAppPractice
{class Program{static void Main(){Boy boy = new Boy();//事件响应者Girl girl = new Girl();Timer timer = new Timer();//事件拥有者timer.Interval = 500;//事件订阅、事件成员timer.Elapsed += boy.Action;//Elapsed是timer的一个事件。+=操作符用于让对象订阅一个事件timer.Elapsed += girl.Action;timer.Start();Console.ReadLine();}}class Boy{//可以使用alt+enter让系统帮你创造一个这个事件处理器//事件处理器internal void Action(object sender, ElapsedEventArgs e){Console.WriteLine("I'm running happily!");}}class Girl{internal void Action(object sender, ElapsedEventArgs e){Console.WriteLine("I'm drawing happily!");}}
}

感觉很像是多播委托

接下来,我们看看具体的三种模式的代码实例。

事件的拥有者跟事件的响应者是两个不同的对象

/*
代码功能:让form显示出来,点击一下后关闭
*/
namespace ConsoleAppPractice
{class Program{static void Main(){Form form = new Form();//事件的拥有者Controller controller = new Controller(form);//事件的响应者form.ShowDialog();}}class Controller{private Form form;public Controller(Form form){this.form = form;this.form.Click += this.Action;//订阅事件}private void Action(object sender, EventArgs e)//事件处理器。注意此处事件参数不同于上一例{this.form.Close();}}
}

注意,此处如果进行如下修改:

            Form form = new Form();Controller controller = new Controller();form.ShowDialog();public Controller(){this.form = new Form();this.form.Click += this.Action;//订阅事件}

是不能完成任何功能的。这也体现了第一种模式和第三种模式的区别。虽然都是在构造器里进行初始化和事件订阅,但是由于第一个模式,是用一个对象去响应另一个对象,故必须传递事件拥有者的地址,才能让事件被正确订阅;第三个模式只有自己一个对象,所以只需创建自己的实例即可。

第三个模式例子中的私有字段成员button和textBox之所以能被外界访问,我初步猜想是由于:

            this.Controls.Add(this.button);this.Controls.Add(this.textBox);

这两条语句。

我们仍希望使用Form这个类,因而我们需要对Form这个类添加事件处理器。

但是Form这个类是微软自带的类,不能被修改。所以,我们可以使用拓展方法或者派生类,来为其添加方法。

具体代码如下:

//使用派生类
namespace ConsoleAppPractice
{class Program{static void Main(){MyForm form = new MyForm();//事件的拥有者、订阅者form.Click += form.Action;//订阅事件form.ShowDialog();}}class MyForm : Form{internal void Action(object sender, EventArgs e)//事件处理器。事件参数于上一例相同{this.Text = "Hello World!";}}
}
//使用扩展方法
namespace ConsoleAppPractice
{class Program{static void Main(){Form form = new Form();form.Click += form.Action;form.ShowDialog();}}static class FormExtension{internal static void Action(this Form form,object sender, EventArgs e){form.Text = "You clicked me just now.>///<";}}
}

事件的拥有者是事件的响应者的一个字段成员。

namespace ConsoleAppPractice
{class Program{static void Main(){MyForm form = new MyForm();form.ShowDialog();}}class MyForm : Form{private Button button;private TextBox textBox;public MyForm(){this.button = new Button();this.textBox = new TextBox();this.Controls.Add(this.button);this.Controls.Add(this.textBox);button.Top = 50;button.Click += this.ClickEvent;}private void ClickEvent(object sender, EventArgs e){this.textBox.Text = "Hello!You cliked me just now.";}}
}

下面我们来看例子,通过其看事件的自定义声明。

这例子的实际背景:一个顾客走进来,服务员迎接,并问要点什么菜。顾客说明菜名和尺寸后,服务员接受订单并计算账单价格,随后顾客坐下来吃饭,最后付款。

运行效果:

由之可见,需要两个事件。一个是顾客走进来,服务员迎接;一个是顾客点菜,服务员计算账单。这两个事件当然C#没有提供,所以需要我们自己声明。

具体代码如下:

using System;
using System.Windows.Forms;
using System.Threading;namespace ConsoleAppPractice
{public delegate void WalkInEventHandler(Customer customer, EventArgs e);public delegate void OrderEventHandler(Customer customer, OrderEventArgs dish);class Program{static void Main(){Thread.Sleep(2000);Sever sever = new Sever();Customer customer = new Customer();customer.Action(sever);}}public class Customer{public int Bill { get; set; }public void Action(Sever sever)//实现代码基本逻辑{this.WalkIn += sever.WelcomeAction;this.Walking();Thread.Sleep(3000);this.Order += sever.OrderAction;this.OrderDish();this.Eating();this.PayTheBill(this.Bill);}private WalkInEventHandler walkInEventHandler;//声明事件1:走进来了public event WalkInEventHandler WalkIn{add{this.walkInEventHandler += value;}remove{this.walkInEventHandler -= value;}}protected void Walking()//事件触发的内部逻辑,使用protected保护{Console.WriteLine("I am walking in!");Thread.Sleep(2000);this.Sit();EventArgs e = new EventArgs();this.walkInEventHandler.Invoke(this,e);//触发事件}public void Sit(){Console.WriteLine("I sat.");}private OrderEventHandler orderEventHandler;//声明事件2:顾客点单public event OrderEventHandler Order{add{this.orderEventHandler += value;}remove{this.orderEventHandler -= value;}}protected void OrderDish()//事件触发内部逻辑,得用protected保护{Console.WriteLine("I will make an order.");OrderEventArgs dish = new OrderEventArgs();this.orderEventHandler.Invoke(this, dish);Sever.CountAction(this, dish);}public void Eating(){for (int i = 0; i < 5; i++){Console.WriteLine("I am eating......");Thread.Sleep(1000);}}public void PayTheBill(int bill){Console.WriteLine("I will pay ${0}",bill);}}public class Sever{public static void CountAction(Customer customer, OrderEventArgs dish){int price = 0;switch (dish.DishName){case "FriedFish":price = 10;break;case "ChickenSoup":price = 19;break;default:break;}switch (dish.DishSize){case "Small":price *= 1;break;case "Big":price *= 2;break;default:break;}customer.Bill += price;Console.WriteLine("We received your order successfully.");Thread.Sleep(5000);Console.WriteLine("The dish is finished.May you have a good eating!");}internal void OrderAction(Customer customer, OrderEventArgs dish){Console.WriteLine("What do you want to eat?");dish.DishName = Console.ReadLine();Console.WriteLine("What size do you want to eat?");dish.DishSize = Console.ReadLine();}internal void WelcomeAction(Customer customer, EventArgs e)//事件1的处理器{Console.WriteLine("Welcome to our restaurant!");Console.WriteLine("Please see the menu.The Big size is twice as the Small size:");Console.WriteLine("\tDishName\tPrice");Console.WriteLine("\tFriedFish\t10");Console.WriteLine("\tChickenSoup\t19");}}public class OrderEventArgs:EventArgs{public string DishName { get; set; }public string DishSize { get; set; }}
}

高亮一下事件声明方法:

    public delegate void OrderEventHandler(Customer customer, OrderEventArgs dish);//声明事件处理器的委托,注意命名方式public class Customer{private OrderEventHandler orderEventHandler;//声明事件处理器的委托字段public event OrderEventHandler Order{add{this.orderEventHandler += value;}remove{this.orderEventHandler -= value;}}protected void OrderDish()//事件触发内部逻辑,使用protected保护{……OrderEventArgs dish = new OrderEventArgs();//声明事件参数,事件要通知的东西this.orderEventHandler.Invoke(this, dish);//触发事件……}public void Action(Sever sever)//实现代码基本逻辑{this.Order += sever.OrderAction;//订阅this.OrderDish();//开始触发事件}}public class Sever{internal void OrderAction(Customer customer, OrderEventArgs dish){……}//事件处理器}public class OrderEventArgs:EventArgs//注意命名方式和父类{……//事件参数的类}

事件有简要的声明,此时连委托也可以懒得命名。

namespace ConsoleAppPractice
{class Program{static void Main(){Console.ReadLine();Phone myPhone = new Phone();Me me = new Me();MessageEventArgs message = new MessageEventArgs();message.CreateMessage();myPhone.m = message;me.Sleeping();Thread.Sleep(1000);myPhone.Ring += me.Surprised;myPhone.Ringing();Thread.Sleep(3000);myPhone.GetMessage += me.Action;myPhone.Action();Console.ReadLine();}}public class Phone{public event EventHandler GetMessage;//使用自带的事件委托类型public event EventHandler Ring;public MessageEventArgs m;protected void OnGetMessage(){Console.WriteLine("My Phone:You have just received a message!");this.GetMessage.Invoke(this,this.m);}protected void OnRing(){Console.WriteLine("My Phone:dingdong~");Thread.Sleep(3000);this.Ring.Invoke(this, new EventArgs());//直接使用事件名。不过当然仅限于此}public void Ringing(){this.OnRing();}public void Action(){this.OnGetMessage();}}public class MessageEventArgs:EventArgs{public string senderName { get; set; }public string content { get; set; }public void CreateMessage(){this.senderName = "Mom";this.content = "What did you eat this morning?You should be healthy!";}}class Me{internal void Action(object sender, EventArgs e){MessageEventArgs m = e as MessageEventArgs;//注意此处的类型转换Phone phone = sender as Phone;Console.WriteLine("Me:I received a message from {0}.",m.senderName);Console.WriteLine("Me:The content is:{0}",m.content);}public void Sleeping(){for (int i = 0; i < 5; i++){Console.WriteLine("Me:I am sleeping......");Thread.Sleep(1000);}}internal void Surprised(object sender, EventArgs e){Console.WriteLine("Me:woc!?");}}
}

如果去掉event,成为完全的委托字段,会使其在类外也能被访问,安全性受损。故事件实质上是委托字段的包装器,集结了有逻辑关系的委托组,并将其封装,保护它的安全性,仅向外界暴露了添加/移除事件处理器的办法。

命名规范:

注:关于此处的protected:

如果只想让其被自己的类及自己的类派生出的成员访问,则用protected。private不能被派生子类访问。

可以用如下的代码修改试试。

namespace ConsoleAppPractice
{class Program{static void Main(){B b = new B();b.haha();}}class A{protected void HAHA(){Console.WriteLine("Hh");}}class B : A{public void haha(){this.HAHA();}}
}

C#学习(10)-----事件相关推荐

  1. [jQuery学习系列四 ]4-Jquery学习四-事件操作

    [jQuery学习系列四 ]4-Jquery学习四-事件操作 前言: 今天看知乎偶然看到中国有哪些类似于TED的节目, 回答中的一些推荐我给记录下来了, 顺便也在这里贴一下: 一席 云集 听道 推酷 ...

  2. 深度学习-10:人工智能简史及三剑客

    深度学习-10:人工智能简史及三剑客 深度学习原理与实践(开源图书)-总目录 1 人工智能简史 深度学习理论的突破和深度学习硬件加速能力的突破,使AI在模式识别.无人驾驶.智力游戏领域取得空前的成功. ...

  3. Android学习按键事件监听与Command模式

    Android学习按键事件监听与Command模式 - Dufresne - 博客园 Android学习按键事件监听与Command模式 一 Command模式 意图: 将一个请求封装为一个对象,从而 ...

  4. 深度学习(10):自然语言处理(转)

    切换站点概览 目录 你已经读了46% 1. 词嵌入 2. 词嵌入方法 2.1. 神经概率语言模型 2.2. Word2Vec 2.3. GloVe 3. 词嵌入应用:情感分类器 4. 词嵌入除偏 5. ...

  5. JavaScript学习10 JS数据类型、强制类型转换和对象属性

    JavaScript学习10 JS数据类型.强制类型转换和对象属性 JavaScript数据类型 JavaScript中有五种原始数据类型:Undefined.Null.Boolean.Number以 ...

  6. html学习 - jquery事件监听详解

    html学习 - jquery事件监听详解 html学习 - jquery事件监听详解 监听方法 监听方法参数解释 click参数 事件自动执行问题解决 bind方法 live方法 监听方法 在jqu ...

  7. ScalersTalk 机器学习小组第 21 周学习笔记(深度学习-10)

    ScalersTalk 机器学习小组第 21 周学习笔记(深度学习-10) Scalers点评:机器学习小组是成长会的内部小组,这是成长会机器学习小组第21周学习笔记,也是深度学习第10次的复盘笔记 ...

  8. ROS2_Foxy学习10——多机激光SLAM准备篇

    ROS2_Foxy学习10--多机激光SLAM准备篇 1 安装Ubuntu20.04 mate 2 安装ROS noetic 3 安装cartographer 4 详细配置cartographer 5 ...

  9. 模电学习10. MOS管简单应用电路

    模电学习10. MOS管简单使应用电路 一.开关和放大器 1. 开关电路 2. 放大电路 二.时序电路中作为反相器使用 三.双向电平转换电路 1. 原理图 2. 工作状态分析 (1)分析SDA,信号从 ...

  10. Nmap学习10 - 对目标主机使用 NSE 脚本

    Nmap学习10 - 对目标主机使用 NSE 脚本 脚本类别 --script 使用脚本 使用类别 brute 枚举.暴力破解 使用多个类别 使用文件名 dns-brute 枚举子域名 broadca ...

最新文章

  1. ccache编译器缓存使用方法
  2. NOP 指令作用[转]
  3. git如何添加远程主机_Git远程操作详解
  4. 【Android 逆向】函数拦截 ( CPU 高速缓存机制 | CPU 高速缓存机制 导致 函数拦截失败 )
  5. 《C++语言基础》程序阅读——和对象找感觉
  6. PowerDesigner运行自定义VBS脚本,复制Name到Comment
  7. metasploit渗透测试指南_Metasploit渗透测试环境搭建与使用
  8. 智能合约最佳实践 之 Solidity 编码规范
  9. 解决类似 /usr/lib64/libstdc++.so.6: version `GLIBCXX_3.4.21' not found 的问题
  10. OpenCV-基本图形绘制(圆、矩形、椭圆)
  11. [转载]Spring Boot Actuator 使用
  12. golang中base64编码_Rust 中的字符集编码 Rust 实践指南
  13. UTC时间转北京时间原理与matlab代码
  14. 10月24日——程序猿的节日
  15. java中无法解析的编译问题怎么解决,java.lang.Error: 无法解析的编译问题
  16. 微信美团服务器开小差,“美团无法使用微信支付”耽误大家干饭,客服:异常已经解决...
  17. 网页版数据库管理工具安装教程——phpAdmin
  18. Android集成百度语音识别
  19. EDA原理及应用 Quartus II 13建工程 个人笔记
  20. 第063讲: 论一只爬虫的自我修养11:Scrapy框架之初窥门径 | 学习记录(小甲鱼零基础入门学习Python)

热门文章

  1. 北京,有2000万人假装在生活
  2. 服务器ras6000系列,再看IBM System x M3系列服务器的RAS特性
  3. 新品爆款打造流程与操作步骤--电商人必看
  4. 计算机一级经验分享,计算机一级考试经验
  5. educoder头歌Web实训 web课——综合应用案例:动态焦点图页面的制作
  6. PHPCMS推荐位图片频道首页焦点图
  7. 解决电脑C盘空间不足,发现微信和qq文件占用了大量内存
  8. shell 统计单词频率
  9. 打包文件zip压缩包返回
  10. 黑苹果html5视频卡顿,W7系统如何设置开机自动连接宽带