C#入门 020 事件(类型成员)
初步了解事件
定义:单词Event,译为“事件”
- 《牛津词典》中的解释是“a thing that happens, especially something important
- 通顺的解释就是“能够发生的什么事情”
角色:使对象或类具备通知能力的成员
- 事件(event)是一种使对象或类能够提供通知的成员
- 对象O拥有一个事件E"想表达的思想是:当事件E发生的时候,O有能力通知别的对象
使用:用于对象或类间的动作协调与信息传递(消息推送)
原理:事件模型(event model)中的两个"5
"发生>响应”中的5个部分--闹钟响了你起床、孩子饿了你做饭.…这里隐含着“订阅”关系"
发生→响应"中的5个动作--
- (1)我有一个事件→
- (2)一个人或者一群人关心我的这个事件>
- (3)我的这个事件发生了→
- (4)关心这个事件的人会被依次通知到>
- (5)被通知到的人根据拿到的事件信息(又称“事件数据”、“事件参数”、“通知”)对事件进行响应(又称“处理事件”)
提示
- 事件多用于桌面、手机等开发的客户端编程,因为这些程序经常是用户通过事件来“驱动”的务种编程语言对这个机制的实现方法不尽相同
- Java语言里没有事件这种成员,也没有委托这种数据类型。Java的“事件”是使用接口来实现的MVC、MVP、MVVM等模式,是事件模式更高级、更有效的“玩法
- 日常开发的时候,使用已有事件的机会比较多,自己声明事件的机会比较少,所以先学使用
事件的应用
实例演示
派生(继承)与扩展(extends)。
事件模型的五个组成部分
- 事件的拥有者(event source,对象) 主体、消息的发送者 ,
- 事件不会主动发生 , 是被拥有者内部逻辑触发之后才能发生
- 谁调用事件谁就是事件的拥有者
- 事件成员(event,成员) 事件本身
- 事件的响应者(event subscriber,对象)
- 事件处理器(event handler,成员)一本质上是一个回调方法4.
- 事件订阅--把事件处理器与事件关联在一起,本质上是一种以委托类型为基础的“约定”
注意
- 事件处理器是方法成员
- 挂接事件处理器的时候,可以使用委托实例,也可以直接使用方法名,这是个“语法糖”
- 事件处理器对事件的订阅不是随意的,匹配与否由声明事件时所使用的委托类型来检测
- 事件可以同步调用也可以异步调用
深入理解事件
两星
- 一个对象拿着自己的方法去订阅和处理自己的事件
- 事件的拥有者和事件的响应者是同一个对象
C#自带的类(比如Form)都不能在添加事件
三星
- 事件拥有者是事件响应者的一个字段成员
- 事件响应者用自己的方法订阅着自己的字段成员的某个事件
注意
- 事件处理器是方法成员
- 挂接事件处理器的时候,可以使用委托实例,也可以直接使用方法名,这是个“语法糖”
- 事件处理器对事件的订阅不是随意的,匹配与否由声明事件时所使用的委托类型来检测
- 事件可以同步调用也可以异步调用
事件的声明
完整声明
- 事件需要委托类型来做一个约束,这个约束规定了事件能发送什么消息给响应者,也规定了事件的响应者能收到什么类型的消息
- 当事件响应者向事件拥有者提供了能够匹配这个事件的事件处理器之后,需要把事件处理器保存或者记录下来。能够记录或者说引用方法的任务,只有委托类型的实例能够做到。
简略声明(字段式声明,field-like )
声明示例
首先,我们定义了一个OrderEventArgs类,它继承自.NET的EventArgs类。这个类用于传递订单的信息(菜名和大小)。
接着,我们定义了OrderEventHandler委托类型,它接受两个参数:一个Customer对象和一个OrderEventArgs对象。
然后是Customer类,它有一个名为Order的事件,该事件就是之前定义的OrderEventHandler类型。当顾客点餐时,就会触发这个事件。Customer类还有几个方法模拟顾客的行为,如进入餐厅、坐下、思考点什么菜,以及最后付账。
Think方法中,顾客思考要吃什么,并通过orderEventHandler调用事件处理程序,这里实际上是通知服务员(Waiter类)他们点了什么菜。
Waiter类有一个内部方法Action,这个方法作为事件处理程序被注册到Customer的Order事件上。当顾客点餐后,服务员根据订单计算价格并更新顾客的账单。
最后,在Program类的Main方法里,创建了一个顾客实例和一个服务员实例,并将服务员的Action方法注册为顾客Order事件的处理程序。然后模拟顾客进店、坐下、思考并点餐的过程,最后付账。
总结来说,这段代码演示了如何使用事件来实现对象之间的通信。当一个对象(这里是Customer)发生了一些事情(例如点餐),它可以通知其他对象(这里是Waiter)来执行相应的操作(如记录订单和计算费用)。这是一种解耦的设计模式,使得对象之间不需要直接相互引用就可以进行通信。
using System;
using System.Threading;namespace 刘铁猛
{internal class Program{static void Main(string[] args){// 创建顾客实例Customer customer = new Customer();// 创建服务员实例Waiter waiter = new Waiter();// 注册服务员的动作作为顾客点餐事件的处理程序customer.Order += waiter.Action;// 顾客行动开始customer.Action();// 顾客付账customer.PayTheBill();Console.ReadLine(); // 暂停程序,等待用户输入}}// 定义订单事件参数类public class OrderEventArgs : EventArgs {public string DishName { get; set; } // 菜名public string Size { get; set; } // 大小}// 定义委托类型,用于处理订单事件public delegate void OrderEventHandler(Customer customer, OrderEventArgs e);// 顾客类public class Customer{ //pbulic event OrderEventHandler Order;简略声明事件 示例private OrderEventHandler orderEventHandler; // 存储事件处理程序的字段// 定义事件Order,允许外部对象注册事件处理程序public 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 ...."); // 输出思考的信息Thread.Sleep(1000); // 模拟思考时间,暂停一秒}if (this.orderEventHandler != null) // 如果有事件处理程序注册{OrderEventArgs e = new OrderEventArgs(); // 创建订单事件参数实例e.DishName = "辣椒炒肉"; // 设置菜名e.Size = "large"; // 设置大小this.orderEventHandler.Invoke(this, e); // 触发事件,通知服务员} //简略声明/*if (this.orderEventHandler != null) // 如果有事件处理程序注册{OrderEventArgs e = new OrderEventArgs(); // 创建订单事件参数实例e.DishName = "辣椒炒肉"; // 设置菜名e.Size = "large"; // 设置大小this.orderEventHandler.Invoke(this, e); // 触发事件,通知服务员}*/}// 主动作方法public void Action(){Console.ReadLine(); // 暂停,等待用户确认this.walkIn(); // 顾客进入餐厅this.sitDown(); // 顾客坐下this.Think(); // 顾客思考点什么菜}}// 服务员类public class Waiter{// 处理顾客点餐的方法internal void Action(Customer customer, OrderEventArgs e){Console.WriteLine("I will 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; // 更新顾客账单}}
}
有了委托字段/属性,为什么还需要事件 ?
- 为了程序的逻辑更加“有道理”、更加安全,谨防”借刀杀人”
所以事件的本质是委托字段的一个包装器
- 这个包装器对委托字段的访问起限制作用,相当于一个“蒙板”
- 封装(encapsulation)的一个重要功能就是隐藏
- 事件对外界隐藏了委托实例的大部分功能,仅暴露添加/移除事件处理器的功能
- 添加/移除事件处理器的时候可以直接使用方法名,这是委托实例所不具备的功能
用于声明事件的委托类型的命名约定
- 用于声明Foo事件的委托,一般命名为FooEventHandler(除非是一个非常通用的事件约束)
- 凡是传递数据的类,都从EventArge类派生出来
- FooEventHandler委托的参数一般有两个(由Win32 API演化而来,历史悠久)
- 第一个是object类型,名字为sender,实际上就是事件的拥有者、事件的source。
- 第二个是EventArgs类的派生类,类名一般为FooEventArgs,参数名为e。也就是前面讲过的事件参数
- 虽然没有官方的说法,但我们可以把委托的参数列表看做是事件发生后发送给事件响应者的“事件消息
- 触发Foo事件的方法一般命名为OnFoo,即“因何引发”、“事出有因”
- 访问级别为protected,不能为public,不然又成了可以“借刀杀人”了
事件的命名约定
带有时态的动词或者动词短语
事件拥有者”正在做”什么事情,用进行时;事件拥有者“做完了”什么事情,用完成时
问题辨析