初步了解事件
事件的功能 = 通知 + 可选的事件参数(即详细信息)
-
定义:单词 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;
namespace EventExample
{
class Program
{
static void Main(string[] args)
{
Timer timer = new Timer();
timer.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;
namespace EventExample
{
class Program
{
static void Main(string[] args)
{
Form form = new Form();
Controller controller = new Controller(form);
form.ShowDialog();
}
}
class Controller
{
private Form form;
public Controller(Form form)
{
if (form != null)
{
this.form = form;
this.form.Click += this.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)
{
MyForm form = new MyForm();
form.Click += form.FormClicked;
form.ShowDialog();
}
}
class MyForm : Form
{
internal void FormClicked(object sender, EventArgs e)
{
this.Text = DateTime.Now.ToString();
}
}
}
(4)事件拥有者是事件响应者的一个字段成员 (最常用)☆☆☆
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);
this.button3.Click += this.ButtonClicked;
this.button3.Click += delegate (object sender, EventArgs e)
{
this.textBox1.Text = "Haha!";
};
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();
}
}
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
{
private OrderEventHandler orderEventHandler;
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 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();
}
}
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 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)包装器永远不可能是被包装的东西
|