0%

【C#】事件详解

>无扰阅读模式(测试)

事件

[Event]


发生→相应”的五个动作

  • 事件是使对象或类具备通知能力的成员
  • 事件功能=功能+参数(可选)
  • 使用:用于对象或类间的动作协调与信息传递(消息推送)

①原理

事件模型中的两个“5”:

“发生→相应” 中的五个部分:

“闹钟1 响了2 我3 起床4”

1发生事件的对象 2事件的内容 3对事件做出响应的对象 4对事件做出响应的对象的响应内容

5订阅关系

“发生→相应”的五个动作:

⑴我有一个事件→⑵一个人或一群人关心我的这个事件→⑶事件发生了→⑷关心这个事件的人被依次通知→⑸被通知的人根据拿到的事件参数对事件进行响应

提示: 1. 事件多用于桌面、手机等开发客户端的编程,因为这些程序经常是用户通过事件来“驱动”的。 1. 各编程语言对这个机制实现的方法不尽相同,Java语言无事件成员也无委托这种数据类型,其“事件”是使用接口实现的。 1. MVC、MVP、MVVM等模式,是事件模式更高级、更有效的玩法。 1. 日常开发的时候,使用已有事件的机会比较多,自己声明事件的机会比较少。 ## ②事件的应用(使用事件)

功能

实现形式

事件的拥有者

对象

事件成员

成员

事件响应者

对象

事件处理器

成员

事件订阅

(委托)*

*把事件处理器与事件关联在一起,本质上是一种委托类型为基础的约定

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Program
{
static void Main(string[] args)
{
Timer timer=new Timer();//定义一个事件的拥有着
timer.Interval=1000;//设置时钟每1s触发一次Elapsed事件
Boy boy=new Boy();//定义一个事件的响应者
timer.Elapsed+=boy.Action;//用+=订阅事件
//↑事件 ↑ ↑事件的处理器
// ┕事件的响应者
timer.Start();//启用时钟
}
}
class Boy
{
internal void Action(object sender,ElapsedEventArgs e)
{
Console.Writeline("Jump!");
}
//事件处理器,建议在订阅事件中自动生成(Ctrl+.+Enter),否则不易确定参数
}

两种挂接方式

1
2
this.button.Click+=new EventHandler(this.ButtonClicked);
this.button.Click+=this.ButtonClicked;

③事件的声明(自定义事件)

四种事件构建形式:

示例(使用简化版,注释掉的为标准版):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
using System;
using System.Threading;

namespace @event
{
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//并不强制,但规定凡事件的信息需继承自EventArgs类
{
public string DishName { get; set; }//菜名
public string Size { get; set; }//分量
}

/*声明订餐的委托类,注意这是个类,不要声明在其他类中*/
public delegate void OrderEventHander(Customer customer, OrderEventArgs e);

/*声明顾客类,其包含以下信息
订餐事件、走进餐馆、坐下、思考、呼叫服务员点餐的方法,并有付账方法以及一个记录款项总额的字段用于记录服务员提供的订单*/
public class Customer
{
private OrderEventHander orderEventHander;//声明委托成员,用于记录收听订餐事件的方法


/* //这里是标准的事件定义方法

public event OrderEventHander Order//声明点餐事件(事件成员)
{
add//添加收听该事件的方法
{
this.orderEventHander += value;
}

remove//移除收听该事件的方法
{
this.orderEventHander -= value;
}
//这里很像属性
}
*/
public event OrderEventHander Order;//语法糖

public double Bill { get; set; }//声明一个用于记录服务员回传款项的字段
public void PayTheBill()
{
Console.WriteLine("Customer:I will pay ${0}.", this.Bill);
}//顾客埋单

public void Walkin()
{
Console.WriteLine("Customer:Walk in the restaurant.");
}//顾客走进餐馆

public void SetDown()
{
Console.WriteLine();
}//顾客坐下

public void Think()
{
for (int i = 0; i < 5; i++)
{
Console.WriteLine("Customer:Let me think...");
Thread.Sleep(1000);
}
//顾客想了5秒
/* //标准的事件调用方法
if (this.orderEventHander != null)//排除无方法收听的情况
{
OrderEventArgs e = new OrderEventArgs();//实例化一个订单信息
e.DishName = "Kongpao Chicken";//顾客点了宫保鸡丁
e.Size = "large";//大份宫保鸡丁
this.orderEventHander.Invoke(this, e);//呼叫服务员(执行订餐委托)
*/
if (this.Order != null)//排除无方法收听的情况
//这里的调用order事件实为语法糖,实际应该是一个委托
{
OrderEventArgs e = new OrderEventArgs();//实例化一个订单信息
e.DishName = "Kongpao Chicken";//顾客点了宫保鸡丁
e.Size = "large";//大份宫保鸡丁
this.Order.Invoke(this, e);//呼叫服务员(执行订餐委托)
//这里的Order也是语法糖
}
}//顾客思考并下单

public void Action()//顾客进店以来点餐前的行动
{
this.Walkin();
this.SetDown();
this.Think();
}
}

/*声明一个服务员类,包含以下信息:
服务员收听到顾客的召唤(执行订餐委托)所执行的方法*/
public class Waiter
{
public void Action(Customer customer, OrderEventArgs e)//收听订餐委托的方法,接受顾客身份和一道菜的订单
{
Console.WriteLine("Waiter:I will serve you the dish -{0}", e.DishName);//为顾客报菜名
double price = 10;//设置基准价格
switch (e.Size)//大份菜是基准价格的1.5倍,小份菜是基准价格的0.5倍
{
case "large":
price *= 1.5;
break;
case "small":
price *= 0.5;
break;
default: break;
}

customer.Bill += price;//为顾客的账单添加一道菜的款项
}
}

/*
* 事件的拥有者:Customer
* 事件成员:Order
* 事件的响应者:Waiter
* 事件处理器:wait.Action(Customer customer, OrderEventArgs e)
* 事件订阅:customer.Order += waiter.Action;
*/
}

运行结果:

归纳总结:

  • 事件event与委托类型字段的区别:语义化,防止滥用
  • 事件的本质:
    1. 是委托字段的一个包装器
    2. 该包装器对委托字段的访问起限制作用,相当于一个蒙版
    3. 封装的一个重要的功能就是隐藏
    4. 事件对外界隐藏了委托实例的大部分功能,仅暴露添加/移除事件访问器的功能
  • EventHandle委托:.Net定义好的常用事件:

    1
    2
    public delegate void EventHandler(object sender,EventArgs e)
    // ↑无返回值 ↑两个最基本的类型↑

    使用时需进行类型转换:

    例:xxxEventArgs args=e as xxxEventArgs //e原为EventArgs类型

  • 用于声明事件的委托类型的命名约定:

    • 用于声明Foo事件的委托,一般命名为FooEventHandler
    • FooEventHandler委托的参数一般有两个(由win32演化而来)
    • 第一个是object类型,名字为sender,实际上是事件的拥有者,事件的source
    • 第二个是EventArgs类的派生类,类名一般为FooEventArgs,参数为e,也就是事件参数
    • 虽无官方说法,但我们可以把委托的参数列表看作是事件发生以后发送给事件响应者的“事件消息”
    • 触发Foo事件的方法一般命名为OnFoo即“因何而发”、“事出有因”
    • 访问级别为protected,不应为public
  • 事件的命名约定:
    • 带有时态动词或动词短语
    • 事件拥有者“正在做”什么事,用进行时,事件拥有者“做完了”什么事,用完成时

④后记: