0%

真会C#?——事件Event

什么是事件

  • 事件是一种使对象或类能够提供通知的成员,在C#中扮演了使对象或类具备通知能力的成员的角色,本质上是委托的包装器,比如:手机铃声响了,会通知你该起床了,这时,就可以说事件使手机具备了通知的能力。
  • 事件的功能 = 通知 + 可选的事件参数,事件的事件参数使可选的,比如:手机邮件铃声响,这就通知你有一封邮件发送过来,而邮件就是通知携带的额外信息,即为事件参数(EventArgs)
  • 而事件的功能就是用于对象或类间的动作协调与信息传递

事件模型的组成部分

  • 事件模型的五个组成部分:

    1. 事件的拥有者(eventsource,对象)。
    2. 事件成员(event,成员)。
    3. 事件的订阅者(event subscriber,对象)。
    4. 事件处理器(event handler,成员)——本质上是一个回调方法。
    5. 事件订阅——把事件处理器和事件关联在一起,本质上是一种以委托类型为基础的“约定”。
  • 事件模型中的五个动作:

    1. 我拥有一个事件(事件字段)。
    2. 一个人或一群人关心我这个事件(订阅)。
    3. 我这个事件发生了(事件的触发)。
    4. 关心这个事件的人会被依次通知到(通知顺序和订阅顺序一致)。
    5. 被通知到的人根据拿到的事件信息(EventArgs或者通知本身)对事件进行响应(又称“处理事件”)。
  • 事件模型常见的四种形式:

    1. 事件的拥有者和事件的订阅者无关联

    Snipaste_2020-09-22_09-45-04

    1. 事件的拥有者是事件订阅者的一个成员

    Snipaste_2020-09-22_09-45-16

    1. 事件的订阅者是事件拥有者的一个成员

    Snipaste_2020-09-22_09-45-23

    1. 事件的拥有者同时也是事件的订阅者

    Snipaste_2020-09-22_09-45-31

事件的四个形式的例子

  • 事件的拥有者和事件的订阅者无关联
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
using System;
using System.Timers;
namespace EventDemo
{
class Program
{
static void Main(string[] args)
{
//事件的拥有者
Timer timer = new Timer();
//timer时间间隔的长度,每过一秒钟就会触发一次事件
timer.Interval = 1000;
//事件的订阅者
var boy = new Boy();
var girl = new Girl();
//timer.Elapsed是Timer提供的事件成员,而+=操作就是事件的订阅
//事件处理的顺序和订阅的顺序一致
timer.Elapsed += boy.Action;
timer.Elapsed += girl.Action;

//开启timer,使触发事件
timer.Start();
Console.ReadLine();
}
}
class Boy
{
//事件的处理器,事件和处理器之间必须遵循着一些约定,即委托的类型
//这个委托类型是参数列表为下面列表的委托类型
public void Action(object sender, ElapsedEventArgs e)
{
Console.WriteLine("Jump!");
}
}
class Girl
{
public void Action(object sender, ElapsedEventArgs e)
{
Console.WriteLine("Sing!");
}
}
}
  • 事件的拥有者是事件订阅者的一个成员,在这里需要手动引用System.Windows.Forms程序集
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
using System;
using System.Windows.Forms;

namespace EventDemo4
{
class Program
{
static void Main(string[] args)
{
var form = new MyForm();
form.ShowDialog();
}
}
//事件的订阅者
class MyForm : Form
{
private TextBox textBox;
//事件的拥有者
private Button button;

public MyForm()
{
textBox = new TextBox();
button = new Button();
textBox.Width = 1000;
button.Text = "Say Hello";
button.Top = 50;
this.Controls.Add(button);
this.Controls.Add(textBox);

//事件和订阅
this.button.Click += this.ButtonClicked;
}
//处理器
private void ButtonClicked(object sender, EventArgs e)
{
this.textBox.Text = "Hello World!!!!!!!!!!!!!!!!!!!!";
}
}
}
  • 事件的拥有者同时也是事件的订阅者
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
using System;
using System.Windows.Forms;

namespace EventDemo3
{
class Program
{
static void Main(string[] args)
{
var myForm = new MyForm();
//事件的拥有者同时也是事件的订阅者
myForm.Click += myForm.FormClicked;
myForm.ShowDialog();
}
}

class MyForm : Form
{
public void FormClicked(object sender, EventArgs e)
{
this.Text = DateTime.Now.ToString();
}
}
}
  • 注意:一个事件可以关联多个事件处理器,一个事件处理器可以关联多个事件

自定义事件

事件的完整自定义形式

  • 下面例子模拟了客户进餐馆点餐这一个过程,这个过程一共由以下部分组成:
    1. 顾客(事件的拥有者)——Customer
    2. 订单(事件)——Order
    3. 服务员(事件的订阅者/响应者)——Waiter
    4. 对顾客的订单进行的响应(事件处理器)——Waiter.Action
    5. 顾客喊来服务员进行点餐(事件的触发)——Customer.Action
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
using System;
using System.Threading;

namespace WholeEventDefinition
{
class Program
{
static void Main(string[] args)
{
var customer = new Customer();
var waiter = new Waiter();
//订阅,这个订阅在这个例子中并不需要显式的表现出来,因为顾客和服务员本身就存在订单服务这样一种关系
customer.Order += waiter.Action;
customer.Action();
customer.PayTheBill();
}
}

//事件参数,类名一般是事件名+EventArgs,需要派生于EventArgs这个基类
public class OrderEventArgs :EventArgs
{
public string DishName { get; set; }
public string Size { get; set; }
}
/**
* 订单事件处理器的委托类型,用来规范(约束)事件处理器,委托类型名一般是事件名+EventHandler
* 第一个参数是事件的拥有者,也就是eventsource,第二个参数是携带的事件参数,
* 在这里就是顾客要通知服务员说我要点餐,具体点什么餐,就可以放在OrderEventArgs对象里面
* 另外,OrderEventHandler、OrderEventArgs和Customer三者的访问级别必须是在同级别的,
* 因为它们一般是一起使用的。
*/
public delegate void OrderEventHandler(Customer customer, OrderEventArgs e);

//事件的拥有者
public class Customer
{
//委托字段:用来存储或引用那些事件处理器
private OrderEventHandler orderEventHandler;
//事件字段:表示事件本身,使用orderEventHandler来和事件处理器产生关联
public event OrderEventHandler Order
{
//事件处理器的添加器,当使用+=时触发
add
{
this.orderEventHandler += value;
}
//事件处理器的移除器,当使用-=时触发
remove
{
this.orderEventHandler -= value;
}
}
public double Bill { get; set; }
/// <summary>
/// 支付订单
/// </summary>
public void PayTheBill()
{
Console.WriteLine($"I will pay ${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);
}
//若orderEventHandler不为null,含义就是根本没有订阅事件,也就不能触发事件的发生
if (this.orderEventHandler!=null)
{
//准备事件参数
var e = new OrderEventArgs {DishName = "KongPao Chicken", Size = "large"};
this.orderEventHandler.Invoke(this, e);
}
}

public void Action()
{
Console.ReadLine();
this.WalkIn();
this.SitDown();
this.Think();
}

}
//事件的订阅者
class 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;
}

customer.Bill += price;
}
}

}

事件的简略自定义形式

  • 可以将事件的定义简化成下面形式:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//完整的定义
private OrderEventHandler orderEventHandler;
public event OrderEventHandler Order
{
add
{
this.orderEventHandler += value;
}
remove
{
this.orderEventHandler -= value;
}
}
/**
* 简略的定义,这里的Order就包含了储存事件处理器的委托,
* 这里看起来事件就是一种特殊的委托,但事件其实并不是委托
* 它只是用来操作委托的一种类型,它只能进行+=和-=操作
* 如果使用简略方式,则Order在类的内部既代表了委托也代表了事件
*/
public event OrderEventHandler Order;

使用事件的好处

  • 可以看到其实事件并不是必须的了,因为事件可以做的事情委托也可以做,根本就不需要多此一举来定义事件,那为什么要使用事件呢?
    1. 如果使用public的委托字段来进行订阅和调用,那类的内外部都可以对委托字段进行访问,而且委托字段拥有对事件处理器的完全访问权,这样就有可能在类的外部滥用委托字段,从而导致代码的混乱
    2. 使用event的好处是event在类的内外部的操作权限是不一致的,在类的内部它对委托字段拥有完全的访问权,而在类的外部它只能对类内部的委托字段进行+=和-=操作,即订阅和删除订阅操作,这样就保证了类内部的委托字段不会在外部随意地操作,保证委托字段地安全性。
    3. 事件的触发必须由事件的拥有者进行调用,这就避免了外部对其进行一些非法操作。

事件的触发

  • 触发事件的方法一般命名为On+事件名,访问级别为protected,不能为public。
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
public void Think()
{
for (int i = 0; i < 5; i++)
{
Console.WriteLine("Let me think...");
Thread.Sleep(1000);
}
if (this.Order != null)
{
var e = new OrderEventArgs {DishName = "KongPao Chicken", Size = "large"};
this.Order.Invoke(this, e);
}
}
//上面的代码可以改成下面形式
/**********************************************************/
public void Think()
{
for (int i = 0; i < 5; i++)
{
Console.WriteLine("Let me think...");
Thread.Sleep(1000);
}
//若orderEventHandler不为null,含义就是根本没有订阅事件,也就不能触发事件的发生
OnOrder("GongPao Chicken", "large");
}

protected void OnOrder(string dishName, string size)
{
if (this.Order != null)
{
//准备事件参数
var e = new OrderEventArgs { DishName = dishName, Size = size };
this.Order.Invoke(this, e);
}
}

学习资料:B站刘铁猛

-------------本文结束感谢您的阅读-------------