事件是类的成员,是使对象或类具有通知能力的成员。
事件的组成:发布者、订阅者、事件处理程序、触发事件。
事件订阅本质上是一种以委托类型为基础的约定。

事件相关的说明

事件包含了一个私有的委托,但是你无法直接访问委托。
事件中可用的操作比委托少,我们只能添加、删除或调用事件处理程序。
事件被触发时,它调用委托来一次调用调用列表中的方法。
+=、-=是事件唯一允许的操作。
委托类型声明:事件和事件处理程序必须有共同的签名和返回类型,它们通过委托类型进行描述。
事件处理程序声明:订阅者类中会在事件触发时执行的方法声明。他们不一定是显示命名的方法,还可以是Lambda表达式或匿名方法。
事件声明:订阅者必须注册事件才能在事件被触发时得到通知。这是将事件处理程序与事件相连的代码。
触发事件的代码:发布者类中“触发”事件并导致调用注册的所有事件处理程序的代码。

创建发布者类

1
2
3
4
5
class Button{
private int Width = 240;//按钮宽度
private int Height = 120;//按钮高度
private string Name = "蓝色按钮";
}

声明事件委托

订阅者的事件处理程序需要满足该委托。

1
2
3
class Button{
public delegate void ClickEventHandle(Button sender, ClickEventArgs e);//定义委托类型 Button:触发事件的对象ClickEventArgs:事件附带的信息
}

声明事件附带信息

1
2
3
4
public class ClickEventArgs : EventArgs {//ClickEventArgs:事件附带的信息
public int Width { get; private set; }
public int Height { get; private set; }
}

将触发事件时应执行的函数存放于发布者类的事件委托中

1
2
3
class Button{
public ClickEventHandle Event;
}

声明事件

发布者类必须提供事件对象,这需要委托类型和名称。
事件是类的成员,并且必须声明为public,这样其他类和结构才可以在它上面注册事件处理程序。
不能用new来创建该事件的对象。

1
2
3
4
5
6
7
8
9
10
11
class Button{
public event ClickEventHandle Click {
//add添加事件处理程序,remove移除事件处理程序,value外部传入的事件处理程序
add {
this.Event += value;
}
remove {
this.Event -= value;
}
}//event:关键字 ClickEventHandler:委托类型 Click:事件名
}

发布者触发事件

1
2
3
4
5
6
7
8
9
10
11
12
13
class Button{
public async Task Button_Click() {
if (this.Event != null) {
await Console.Out.WriteLineAsync("三秒后自动点击按钮");
await Task.Delay(3000);
ClickEventArgs e = new ClickEventArgs();
e.Width = this.Width;
e.Height = this.Height;
e.Name = this.Name;
this.Event.Invoke(this,e);//触发
}
}
}

创建订阅者类并编写事件处理程序

1
2
3
4
5
6
7
8
9
class Screen {
public Screen(Button btn) {
btn.Click += this.ShowButtonInfo;//绑定事件处理程序
}
public void ShowButtonInfo(Button sender, ClickEventArgs e) {//事件处理程序
Console.WriteLine("\n\n\n我是一块屏幕,检测到按钮被点击了,我要显示:");
Console.WriteLine($"按钮名:{e.Name},按钮宽度:{e.Width},按钮高度:{e.Height}");
}
}

事件的完整写法

假设有一个按钮和一个屏幕,三秒后按钮自动按下,屏幕收到按钮按下的事件,在屏幕上显示按钮的信息。
结合上面讲述的内容,构成事件的完整写法:

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
using System.Threading.Tasks;
namespace Test {
internal class Program {
static async Task Main(string[] args) {
Button button = new Button();
Screen screen = new Screen(button);
await button.Button_Click();
}
}
public class ClickEventArgs : EventArgs {//ClickEventArgs:事件附带的信息
public int Width { get; set; }
public int Height { get; set; }
public string Name { get; set; }
}
class Screen {
public Screen(Button btn) {
btn.Click += this.ShowButtonInfo;
}
public void ShowButtonInfo(Button sender, ClickEventArgs e) {//事件处理程序
Console.WriteLine("\n\n\n我是一块屏幕,检测到按钮被点击了,我要显示:");
Console.WriteLine($"按钮名:{e.Name},按钮宽度:{e.Width},按钮高度:{e.Height}");
}
}
class Button {
public ClickEventHandle Event;
private int Width = 240;//按钮宽度
private int Height = 120;//按钮高度
private string Name = "蓝色按钮";
public delegate void ClickEventHandle(Button sender, ClickEventArgs e);//定义委托类型 Button:触发事件的对象ClickEventArgs:事件附带的信息
public event ClickEventHandle Click {
add {
this.Event += value;
}
remove {
this.Event -= value;
}
}//event:关键字 ClickEventHandler:委托类型 Click:事件名
public async Task Button_Click() {
if (this.Event != null) {
await Console.Out.WriteLineAsync("三秒后自动点击按钮");
await Task.Delay(3000);
ClickEventArgs e = new ClickEventArgs();
e.Width = this.Width;
e.Height = this.Height;
e.Name = this.Name;
this.Event.Invoke(this,e);//触发
}
}
}
}

事件的简单写法

对于事件的使用.Net提供了一个标准模式,该标准模式的基础就是System名称空间中声明的EventHandler委托类型。
在之后声明事件就不需要自己定义事件委托类型了。
EventHandler委托需要注意以下几点:
第一个参数用来保存触发事件的对象的引用,由于它是object类型的,所以可以匹配任何类型的实例。
第二个参数用来保存状态信息,指明什么类型适用于该应用程序。
返回类型是void。
EventHandler的第二个参数是EventArgs类的对象,它声明在System名称空间中,但是EventArgs不能传递任何数据,它用于不需要传递数据的事件处理程序,通常会被忽略。如果你希望传递数据,必须声明一个派生自EventArgs的类,并使用合适的字段来保存需要传递的数据。
事件访问器add和remove可以省路,事件名同时担任事件委托和事件名两个职责,可以简单理解成public event EventHandler Click;声明了一个委托类型成员(但其实不是)
了解了上述的概念,我们就可以简化代码:

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
using System.Threading.Tasks;
using System;
namespace Test {
internal class Program {
static async Task Main(string[] args) {
Button button = new Button();
Screen screen = new Screen(button);
await button.Button_Click();
}
}
public class ClickEventArgs : EventArgs {//ClickEventArgs:事件附带的信息
public int Width { get; set; }
public int Height { get; set; }
public string Name { get; set; }
}
class Screen {
public Screen(Button btn) {
btn.Click += this.ShowButtonInfo;
}
public void ShowButtonInfo(object? sender, EventArgs e) {//事件处理程序
ClickEventArgs ClickArg = e as ClickEventArgs;
if (ClickArg != null) {
Console.WriteLine("\n\n\n我是一块屏幕,检测到按钮被点击了,我要显示:");
Console.WriteLine($"按钮名:{ClickArg.Name},按钮宽度:{ClickArg.Width},按钮高度:{ClickArg.Height}");
}
}
}
class Button {
private int Width = 240;//按钮宽度
private int Height = 120;//按钮高度
private string Name = "蓝色按钮";
public event EventHandler Click;
public async Task Button_Click() {
if (this.Click != null) {
await Console.Out.WriteLineAsync("三秒后自动点击按钮");
await Task.Delay(3000);
ClickEventArgs e = new ClickEventArgs();
e.Width = this.Width;
e.Height = this.Height;
e.Name = this.Name;
this.Click.Invoke(this,e);//触发
}
}
}
}