初步了解事件
- 定义:单词Event,译为“事件”
- 《牛津字典》中的解释是“a thing that happens,especially something important”
- 通顺的解释就是“能够发生的什么事情”
- 角色:使对象或类具备通知能力的成员
- (原文)An event is a member that enables an object or class to provide notifications
- (中译)事件是一种使对象或类能够提供通知的成员
- “对象O拥有一个事件E”想表达的思想是:当事件E发生的时候,O有能力通知别的对象
- 使用:用于对象或类间的动作协调与信息传递(消息推送)
- 原理:事件模型(event model)中的两个“5”
- “发生→响应”中的5个部分——闹钟响了你起床,孩子饿了你做饭…这里隐含着“订阅”关系
- “发生→响应”中的5个动作——①我有一个事件→②一个人或者一群人关心我这个事件→③我的这个事件发生了→④关系这个事件的人会依次通知到→⑤被通知到的人根据拿到的事件信息(又称“事件数据”,“事件参数”,“通知”)对事件进行响应(又称“处理事件”)
- 提示
- 事件多用于桌面,手机等开发的客户端编程,因为这些程序经常是用户通过事件来“驱动”的
- 各种编程语言对这个机制的实现方法不尽相同
- Java语言里没有事件这种成员,也没有委托这种数据类型。Java的事件是使用接口来实现的
- MVC,MVP,MVVM等模式,是事件模式更高级,更有效的“玩法”
- 日常开发的时候,使用已有事件的机会比较多,自己声明事件的机会比较少
事件的应用
- 事件模型的五个组成部分:
1.事件的拥有者(event source,对象)
2.事件成员(event 成员)
3.事件的响应者(event subscriber,对象)
4.事件处理器(event handler,成员)——本质上是一个回调方法
5.事件订阅——把事件处理器和事件关联在一起,本质上是一种以委托类型为基础的“约定”
namespace EventExample
{
class Program
{
static void Main(string[] args)
{
Timer timer = new Timer();//事件拥有者:timer
timer.Interval = 1000;
Boy boy = new Boy();//事件响应者:boy
timer.Elapsed += boy.acion;//事件订阅:+=操作符 事件:Elapsed
timer.Start();
Console.ReadLine();
}
}
class Boy
{
//事件处理器
internal void acion(object sender, ElapsedEventArgs e)
{
Console.WriteLine("Jump");
}
}
}
(一)事件的拥有者和事件的响应者时两个完全不同的对象,事件的响应者用事件处理器订阅着事件拥有者的事件,当事件发生后,事件处理器就执行了
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.Clicked;
}
}
private void Clicked(object sender, EventArgs e)
{
this.form.Text = DateTime.Now.ToString();
}
}
}
运行结果:点击窗体任意位置会显示出当前时间
二,事件的拥有者和事件的响应者是同一个对象(一个对象拿着自己的方法处理自己的事件)
namespace EventExample
{
class Program
{
static void Main(string[] args)
{
MyForm myForm = new MyForm();
myForm.Click += myForm.FormClicked;
myForm.ShowDialog();
}
}
class MyForm : Form
{
internal void FormClicked(object sender, EventArgs e)
{
this.Text = DateTime.Now.ToString();
}
}
}
运行结果同上
三,事件拥有者是事件响应者的一个字段成员,事件响应者用自己的方法订阅着自己的字段成员的某个事件
namespace EventExample
{
class Program
{
static void Main(string[] args)
{
MyForm myForm = new MyForm();
myForm.ShowDialog();
}
}
class MyForm : Form
{
private TextBox TextBox;
private Button button;
public MyForm()
{
this.TextBox = new TextBox();
this.button = new Button();
this.Controls.Add(this.button);
this.Controls.Add(this.TextBox);
this.button.Click += this.ButtonClicked;
this.button.Text = "Click";
this.button.Top = 50;
}
private void ButtonClicked(object sender, EventArgs e)
{
this.TextBox.Text = "Hello World!!!!!!!!!!!!";
}
}
}
运行结果:点击按钮会弹出文本框
使用WindowsForm操作系统实现:
namespace WindowsFormsApp1
{
public partial class MyForm : Form
{
public MyForm()
{
InitializeComponent();
this.MyButton.Text = "SayHello!";
this.MyButton.Click += MyButton_Click;
}
private void MyButton_Click(object sender, EventArgs e)
{
this.MyTextBox.Text = "Hello!";
}
}
}
方法的复用以及多种事件挂接方式:
namespace WindowsFormsApp1
{
public partial class MyForm : Form
{
public MyForm()
{
InitializeComponent();
this.button1.Text = "SayHello!";
this.button3.Click += MyButton_Click;//第一种挂接事件方法
this.button3.Click += new EventHandler(this.MyButton_Click);//第二种
this.button3.Click += delegate (object sender, EventArgs e)//第三种 匿名方法
{
this.MyTextBox.Text = "haha";
};
this.button3.Click += (sender,e) => //第四种 lambda表达式
{
this.MyTextBox.Text = "hoho";
};
}
private void MyButton_Click(object sender, EventArgs e)//sender即为事件的source(拥有者)
{
if(sender == this.button1)//两个source订阅了同一个方法 MyButton_Click()
{
this.MyTextBox.Text = "Hello";
}
if (sender == this.button2)
{
this.MyTextBox.Text = "World";
}
if (sender == this.button3)
{
this.MyTextBox.Text = "HaHa";
}
}
}
}
总结:一个事件可以挂接多个事件处理器,一个事件处理器也可以被多个事件所挂接
-
注意
-
事件处理器是方法的成员
-
挂接事件处理器的时候,可以使用委托实例,也可以直接使用方法名,这是个“语法糖”
-
事件处理器对事件的订阅不是随意的,匹配与否由声明事件时所使用的委托类型来检测
-
事件可以同步调用也可以异步调用
事件的声明
-
事件的声明
- 完整声明
namespace EventExample
{
class Program
{
static void Main(string[] args)
{
Customer customer = new Customer();
Waiter waiter = new Waiter();
customer.Order += waiter.Action;//挂接事件。waiter的Action订阅着customer的Order
customer.Action();
customer.PayTheBill();
}
}
public delegate void OrderEventHandler(Customer customer, OrderEventArgs e);//声明一个委托类型,专门用来声明事件,约束事件处理器
public class OrderEventArgs : EventArgs//声明用来传递消息的类,派生自EventArgs
{
public string DishName { get; set; }
public string Size { get; set; }
}
public class Customer //事件发起者
{
private OrderEventHandler OrderEventHandler;//声明委托类型字段。用于存储和引用事件处理器
public event OrderEventHandler Order//声明事件Order。用OrderEventHandler来约束事件
{
add//事件处理器的添加器
{
this.OrderEventHandler += value;
}
remove//事件处理器的移除器
{
this.OrderEventHandler -= value;
}
}
public double Bill { get; set; }
public void Walkin()
{
Console.WriteLine("Walk in 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();
Walkin();
Sitdown();
Think();
}
public void PayTheBill()
{
Console.WriteLine("I Will pay ${0}.",this.Bill);
}
}
public class Waiter //事件的响应者Waiter
{
public void Action(Customer customer, OrderEventArgs e)//事件处理器
{
Console.WriteLine("I will serve you the 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;
}
}
}
运行结果:
- 简略声明(字段式声明,field-like)
namespace EventExample
{
class Program
{
static void Main(string[] args)
{
Customer customer = new Customer();
Waiter waiter = new Waiter();
customer.Order += waiter.Action;//挂接事件。waiter的Action订阅着customer的Order
customer.Action();
customer.PayTheBill();
}
}
public delegate void OrderEventHandler(Customer customer, OrderEventArgs e);//声明一个委托类型,专门用来声明事件,约束事件处理器
public class OrderEventArgs : EventArgs//声明用来传递消息的类,派生自EventArgs
{
public string DishName { get; set; }
public string Size { get; set; }
}
public class Customer //事件发起者
{
public event OrderEventHandler Order;//声明事件Order。用OrderEventHandler来约束事件
public double Bill { get; set; }
public void Walkin()
{
Console.WriteLine("Walk in 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.Order != null)//等于空说明没有人订阅这个事件,会报异常
{
OrderEventArgs e = new OrderEventArgs();
e.DishName = "Kongpao Chicken";
e.Size = "large";
this.Order.Invoke(this, e);
}
}
public void Action()
{
Console.ReadLine();
Walkin();
Sitdown();
Think();
}
public void PayTheBill()
{
Console.WriteLine("I Will pay ${0}.",this.Bill);
}
}
public class Waiter //事件的响应者Waiter
{
public void Action(Customer customer, OrderEventArgs e)//事件处理器
{
Console.WriteLine("I will serve you the 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(除非是一个非常通用的事件约束)
如微软为我们准备好的EventHandler委托类型字段
namespace EventExample
{
class Program
{
static void Main(string[] args)
{
Customer customer = new Customer();
Waiter waiter = new Waiter();
customer.Order += waiter.Action;//挂接事件。waiter的Action订阅着customer的Order
customer.Action();
customer.PayTheBill();
}
}
//public delegate void OrderEventHandler(Customer customer, OrderEventArgs e);//声明一个委托类型,专门用来声明事件,约束事件处理器
public class OrderEventArgs : EventArgs//声明用来传递消息的类,派生自EventArgs
{
public string DishName { get; set; }
public string Size { get; set; }
}
public class Customer //事件发起者
{
public event EventHandler Order;//声明事件Order。用OrderEventHandler来约束事件
public double Bill { get; set; }
public void Walkin()
{
Console.WriteLine("Walk in 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.Order != null)//等于空说明没有人订阅这个事件,会报异常
{
OrderEventArgs e = new OrderEventArgs();
e.DishName = "Kongpao Chicken";
e.Size = "large";
this.Order.Invoke(this, e);
}
}
public void Action()
{
Console.ReadLine();
Walkin();
Sitdown();
Think();
}
public void PayTheBill()
{
Console.WriteLine("I Will pay ${0}.",this.Bill);
}
}
public class Waiter //事件的响应者Waiter
{
public void Action(Object sender, EventArgs e)//事件处理器
{
Customer customer = sender as Customer;
OrderEventArgs orderEventArgs = e as OrderEventArgs;
Console.WriteLine("I will serve you the dish - {0}", orderEventArgs.DishName);
double price = 10;
switch (orderEventArgs.Size)
{
case "small":
price = price * 0.5;
break;
case "large":
price = price * 1.5;
break;
default:
break;
}
customer.Bill += price;
}
}
}
- FooEventHandler委托的参数一般有两个(由Win32 API演化而来,历史悠久)
- 第一个是object类型,名字为sender,实际上就是事件的拥有者,事件的source
- 第二个EvengArgs的派生类,类名一般为FooEventArgs,参数名为e,也就是前面讲过的事件参数
- 虽然没有官方的说法,但我们可以把委托的参数列表看作是事件发生后发送给事件响应者的“事件消息”
- 触发Foo事件的方法一般命名为OnFoo,即“因何引发”,“事出有因”
- 访问级别为protected,不能为public
(对上文代码进行改进):
namespace EventExample
{
class Program
{
static void Main(string[] args)
{
Customer customer = new Customer();
Waiter waiter = new Waiter();
customer.Order += waiter.Action;//挂接事件。waiter的Action订阅着customer的Order
customer.Action();
customer.PayTheBill();
}
}
//public delegate void OrderEventHandler(Customer customer, OrderEventArgs e);//声明一个委托类型,专门用来声明事件,约束事件处理器
public class OrderEventArgs : EventArgs//声明用来传递消息的类,派生自EventArgs
{
public string DishName { get; set; }
public string Size { get; set; }
}
public class Customer //事件发起者
{
public event EventHandler Order;//声明事件Order。用OrderEventHandler来约束事件
public double Bill { get; set; }
public void Walkin()
{
Console.WriteLine("Walk in 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();
Walkin();
Sitdown();
Think();
}
public void PayTheBill()
{
Console.WriteLine("I Will pay ${0}.",this.Bill);
}
}
public class Waiter //事件的响应者Waiter
{
public void Action(Object sender, EventArgs e)//事件处理器
{
Customer customer = sender as Customer;
OrderEventArgs orderEventArgs = e as OrderEventArgs;
Console.WriteLine("I will serve you the dish - {0}", orderEventArgs.DishName);
double price = 10;
switch (orderEventArgs.Size)
{
case "small":
price = price * 0.5;
break;
case "large":
price = price * 1.5;
break;
default:
break;
}
customer.Bill += price;
}
}
}
- 事件的命名约定
- 带有时态的动词或者动词短语
- 事件拥有者“正在做”什么事情,用进行时;事件拥有者“做完了”什么事情,用完成时
事件与委托的关系
- 事件真的是“以特殊方式声明的委托字段/实例吗”?
- 不是!只是声明的时候“看起来像”(对比委托字段的和事件的简化声明,field-like)
- 事件声明的时候使用了委托类型,简化声明造成事件看上去像一个委托的字段(实例),而event关键字则更像是一个修饰符——这就是错觉的来源之一
- 订阅事件的时候+=操作符后面可以是一个委托实例,这与委托实例的赋值方法语法相同,这也让事件看起来像是一个委托字段——这是错觉的又一来源
- 重申:事件的本质是加装在委托字段上的一个“蒙板”(mask),是个起掩蔽作用的包装器,这个用于阻挡非法操作的“蒙板”绝不是委托字段本身
- 为什么要使用委托类型来声明事件?
- 站在source的角度来看,是为了表明source能对外传递哪些消息
- 站在subscriber的角度来看,它是一种约定,是为了约束能够使用什么样签名的方法来处理(响应)事件
- 委托类型的实例将用于存储(引用)事件处理器
- 对比事件和属性
- 属性不是字段——很多时候属性是字段的包装器,这个包装器用来保护字段不被滥用
- 事件不是委托字段——它是委托字段的包装器,这个包装器用于保护委托字段不被滥用
- 包装器永远都不可能是被包装的东西