C#学习笔记【事件Event】

C# 事件(Event)

C# 事件(Event)是一种成员,用于将特定的事件通知发送给订阅者。事件通常用于实现观察者模式,它允许一个对象将状态的变化通知其他对象,而不需要知道这些对象的细节。

事件(Event)

基本上说是一个用户操作,如按键、点击、鼠标移动等等,或者是一些提示信息,如系统生成的通知。应用程序需要在事件发生时响应事件。例如,中断。

C# 中使用事件机制实现线程间的通信。

关键点

  • 声明委托:定义事件将使用的委托类型。委托是一个函数签名。

  • 声明事件:使用 event 关键字声明一个事件。

  • 触发事件:在适当的时候调用事件,通知所有订阅者。

  • 订阅和取消订阅事件:其他类可以通过 += 和 -= 运算符订阅和取消订阅事件。

通过事件使用委托

事件在类中声明且生成,且通过使用同一个类或其他类中的委托与事件处理程序关联。包含事件的类用于发布事件。这被称为 发布器(publisher) 类。其他接受该事件的类被称为 订阅器(subscriber) 类。事件使用 发布-订阅(publisher-subscriber) 模型。

发布器(publisher) 是一个包含事件和委托定义的对象。事件和委托之间的联系也定义在这个对象中。发布器(publisher)类的对象调用这个事件,并通知其他的对象。

订阅器(subscriber) 是一个接受事件并提供事件处理程序的对象。在发布器(publisher)类中的委托调用订阅器(subscriber)类中的方法(事件处理程序)。

声明事件(Event)

在类的内部声明事件,首先必须声明该事件的委托类型。例如:

1
public delegate void BoilerLogHandler(string status);

然后,声明事件本身,使用 event 关键字:

1
2
// 基于上面的委托定义事件
public event BoilerLogHandler BoilerEventLog;

上面的代码定义了一个名为 BoilerLogHandler 的委托和一个名为 BoilerEventLog 的事件,该事件在生成的时候会调用委托。

以下示例展示了如何在 C# 中使用事件:

> 实例:

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
using System;

namespace EventDemo
{
// 定义一个委托类型,用于事件处理程序
public delegate void NotifyEventHandler(object sender, EventArgs e);

// 发布者类
public class ProcessBusinessLogic
{
// 声明事件
public event NotifyEventHandler ProcessCompleted;

// 触发事件的方法
protected virtual void OnProcessCompleted(EventArgs e)
{
ProcessCompleted?.Invoke(this, e);
}

// 模拟业务逻辑过程并触发事件
public void StartProcess()
{
Console.WriteLine("Process Started!");

// 这里可以加入实际的业务逻辑

// 业务逻辑完成,触发事件
OnProcessCompleted(EventArgs.Empty);
}
}

// 订阅者类
public class EventSubscriber
{
public void Subscribe(ProcessBusinessLogic process)
{
process.ProcessCompleted += Process_ProcessCompleted;
}

private void Process_ProcessCompleted(object sender, EventArgs e)
{
Console.WriteLine("Process Completed!");
}
}

class Program
{
static void Main(string[] args)
{
ProcessBusinessLogic process = new ProcessBusinessLogic();
EventSubscriber subscriber = new EventSubscriber();

// 订阅事件
subscriber.Subscribe(process);

// 启动过程
process.StartProcess();

Console.ReadLine();
}
}
}

说明

  • 1、定义委托类型:

    1
    public delegate void NotifyEventHandler(object sender, EventArgs e);

这是一个委托类型,它定义了事件处理程序的签名。通常使用 EventHandler 或 EventHandler 来替代自定义的委托。

  • 2、声明事件:

    1
    public event NotifyEventHandler ProcessCompleted;

这是一个使用 NotifyEventHandler 委托类型的事件。

  • 3、触发事件:

    1
    2
    3
    4
    protected virtual void OnProcessCompleted(EventArgs e)
    {
    ProcessCompleted?.Invoke(this, e);
    }

这是一个受保护的方法,用于触发事件。使用 ?.Invoke 语法来确保只有在有订阅者时才调用事件。

  • 4、订阅和取消订阅事件:

    1
    process.ProcessCompleted += Process_ProcessCompleted;

订阅者使用 += 运算符订阅事件,并定义事件处理程序 Process_ProcessCompleted。

实例

> 实例 1

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
using System;
namespace SimpleEvent
{
using System;
/***********发布器类***********/
public class EventTest
{
private int value;

public delegate void NumManipulationHandler();

public event NumManipulationHandler ChangeNum;
protected virtual void OnNumChanged()
{
if ( ChangeNum != null )
{
ChangeNum(); /* 事件被触发 */
}else {
Console.WriteLine( "event not fire" );
Console.ReadKey(); /* 回车继续 */
}
}


public EventTest()
{
int n = 5;
SetValue( n );
}


public void SetValue( int n )
{
if ( value != n )
{
value = n;
OnNumChanged();
}
}
}


/***********订阅器类***********/

public class subscribEvent
{
public void printf()
{
Console.WriteLine( "event fire" );
Console.ReadKey(); /* 回车继续 */
}
}

/***********触发***********/
public class MainClass
{
public static void Main()
{
EventTest e = new EventTest(); /* 实例化对象,第一次没有触发事件 */
subscribEvent v = new subscribEvent(); /* 实例化对象 */
e.ChangeNum += new EventTest.NumManipulationHandler( v.printf ); /* 注册 */
e.SetValue( 7 );
e.SetValue( 11 );
}
}
}

当上面的代码被编译和执行时,它会产生下列结果:

1
2
3
event not fire
event fire
event fire

本实例提供一个简单的用于热水锅炉系统故障排除的应用程序。当维修工程师检查锅炉时,锅炉的温度和压力会随着维修工程师的备注自动记录到日志文件中。

> 实例 2

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
using System;
using System.IO;

namespace BoilerEventAppl
{
// Boiler 类
class Boiler
{
public int Temp { get; private set; }
public int Pressure { get; private set; }

public Boiler(int temp, int pressure)
{
Temp = temp;
Pressure = pressure;
}
}

// 事件发布器
class DelegateBoilerEvent
{
public delegate void BoilerLogHandler(string status);

// 基于上面的委托定义事件
public event BoilerLogHandler BoilerEventLog;

public void LogProcess()
{
string remarks = "O.K.";
Boiler boiler = new Boiler(100, 12);
int temp = boiler.Temp;
int pressure = boiler.Pressure;

if (temp > 150 || temp < 80 || pressure < 12 || pressure > 15)
{
remarks = "Need Maintenance";
}

OnBoilerEventLog($"Logging Info:\nTemperature: {temp}\nPressure: {pressure}\nMessage: {remarks}");
}

protected void OnBoilerEventLog(string message)
{
BoilerEventLog?.Invoke(message);
}
}

// 该类保留写入日志文件的条款
class BoilerInfoLogger : IDisposable
{
private readonly StreamWriter _streamWriter;

public BoilerInfoLogger(string filename)
{
_streamWriter = new StreamWriter(new FileStream(filename, FileMode.Append, FileAccess.Write));
}

public void Logger(string info)
{
_streamWriter.WriteLine(info);
}

public void Dispose()
{
_streamWriter?.Close();
}
}

// 事件订阅器
public class RecordBoilerInfo
{
static void Logger(string info)
{
Console.WriteLine(info);
}

static void Main(string[] args)
{
using (BoilerInfoLogger fileLogger = new BoilerInfoLogger("e:\\boiler.txt"))
{
DelegateBoilerEvent boilerEvent = new DelegateBoilerEvent();
boilerEvent.BoilerEventLog += Logger;
boilerEvent.BoilerEventLog += fileLogger.Logger;
boilerEvent.LogProcess();
}

Console.ReadLine();
}
}
}

当上面的代码被编译和执行时,它会产生下列结果:

Logging info:

Temperature 100
Pressure 12

Message: O. K

事件是拥有可以注册和解绑方法(函数)的功能。

虽然事件和委托看起来有点绕,只要捋清楚事件和委托的关系,就会很容易理解。

委托是一个类,事件则是委托类中的一个对象,该对象是能够把其他方法注册到委托类中的一个事件(如果觉得有点绕,可以忽略这句话)。

事件和函数的关系:事件具有可以注册多个函数(和解绑函数)的功能,而函数如果要注册和解绑其他在其主体上运行的函数则需要改动该函数本体的代码,这就是区别。

以下代码的大致流程:定义一个新类(事件类)–》类中声明委托–》由委托类又声明事件–》再定义触发事件的函数–》函数主体中执行事件–》在主函数中实例化事件类–》进而调用事件类中的事件对象–》事件对象再注册(+=)两个方法–》再执行事件类中触发事件的那个函数–》再解绑其中一个方法–》再次执行事件类中触发事件的函数。

由此可见:事件是拥有可以注册和解绑方法(函数)的功能。

> 实例3

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
using System;

namespace DelegateAndEvent
{
//定义一个事件类
public class MyEvent
{
//定义一个委托
public delegate void MyDelegate();
//定义一个事件
public MyDelegate MyDelegateEvent;
//定义一个触发事件的函数
public void OnMyDelegateEvent()
{
//判断事件是否非空
if (MyDelegateEvent != null)
{
//执行事件
MyDelegateEvent();
}
//MyDelegateEvent?.Invoke(); //简化的判断和执行
}
}
class Program
{

//输出一串字符
public static void putOutChar()
{
Console.WriteLine("I was fired");
}
//输出第二串字符
public static void putOutChar2()
{
Console.WriteLine("I was fired22222");
}

static void Main(string[] args)
{
//实例化MyEvent2类
MyEvent myEvent = new MyEvent();
//注册一个事件
myEvent.MyDelegateEvent += new MyEvent.MyDelegate(putOutChar);
myEvent.MyDelegateEvent += new MyEvent.MyDelegate(putOutChar2);
//执行触发事件的函数
Console.WriteLine("执行绑定了两个事件后的函数");
myEvent.OnMyDelegateEvent();
//解绑一个事件
myEvent.MyDelegateEvent -= new MyEvent.MyDelegate(putOutChar);
//再次执行触发事件的函数
Console.WriteLine("执行解绑了一个事件后的函数");
myEvent.OnMyDelegateEvent();
Console.ReadKey();
}
}
}