文章目录
- 前言
- [1、 声明委托](#1、 声明委托)
- [2、 声明事件](#2、 声明事件)
- [3、 触发事件](#3、 触发事件)
- 4、订阅和取消订阅事件
- 5、示例展示
前言
在 C# 中,事件(Event)是一种特殊的成员,它扮演着传递特定事件通知给订阅者的重要角色。事件机制常被用于实现观察者模式,借助这一模式,一个对象能够在自身状态发生变化时,将该变化情况通知给其他相关对象,而且无需了解这些接收通知对象的具体细节信息。
从更宽泛的角度来讲,事件可以是用户进行的各类操作,比如按下按键、点击鼠标、移动鼠标等,也可能是系统生成的提示信息之类的内容。应用程序需要具备在事件发生时做出相应响应的能力,就像应对中断情况那样。此外,在 C# 里还可以利用事件机制来实现线程间的通信功能。
事件相关的几个关键操作
1、 声明委托
委托其实就是一个函数签名,在定义事件时,首先得声明该事件将要使用的委托类型。例如:
csharp
public delegate void BoilerLogHandler(string status);
这就定义了一个名为 BoilerLogHandler 的委托,后续的事件会基于这个委托来关联相应的操作逻辑。
2、 声明事件
使用 event 关键字来声明一个事件,像这样:
csharp
// 基于上面的委托定义事件
public event BoilerLogHandler BoilerEventLog;
如此一来,就声明了一个名为 BoilerEventLog 的事件,当这个事件被触发时,会调用与之关联的委托所指向的方法。
3、 触发事件
要在合适的时机去调用事件,从而通知所有已订阅该事件的对象。通常会在类中定义一个受保护的虚方法来触发事件,例如:
csharp
protected virtual void OnProcessCompleted(EventArgs e)
{
ProcessCompleted?.Invoke(this, e);
}
这里使用 ?.Invoke 语法是为了确保只有在存在订阅者的情况下才去调用事件,避免出现空引用异常等问题。
4、订阅和取消订阅事件
其他类能够通过 += 和 -= 运算符来分别实现订阅和取消订阅事件的操作。比如:
csharp
process.ProcessCompleted += Process_ProcessCompleted;
这行代码就是订阅者使用 += 运算符订阅事件,并指定了对应的事件处理程序 Process_ProcessCompleted。
发布 - 订阅模型中的角色
发布器(publisher)
发布器是一个包含事件以及委托定义的对象,并且事件和委托之间的关联也是在这个对象中定义好的。发布器类的对象负责调用事件,进而通知其他相关对象。简单来说,它就是事件的源头,负责向外发送事件通知。
订阅器(subscriber)
订阅器则是接受事件并提供相应事件处理程序的对象。在发布器类中的委托会去调用订阅器类中定义的方法(也就是事件处理程序),以此来对发生的事件做出具体的处理动作。
5、示例展示
示例一:基础的事件使用流程
以下示例代码展示了在 C# 中较为典型的事件使用方式:
csharp
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();
}
}
}
在这个示例中:
首先定义了 NotifyEventHandler 委托类型,它规定了事件处理程序的签名,不过在实际中也常常用 EventHandler 或 EventHandler 来替代自定义的委托。
接着 ProcessBusinessLogic 类作为发布者,声明了 ProcessCompleted 事件,并通过 OnProcessCompleted 方法来触发该事件。
而 EventSubscriber 类作为订阅者,通过 Subscribe 方法使用 += 运算符订阅事件,并定义了具体的事件处理程序 Process_ProcessCompleted。在 Main 方法中完成订阅操作后启动业务逻辑过程,当业务逻辑完成触发事件时,订阅者的事件处理程序就会被调用执行相应输出。
示例二:简单数值变化触发事件
csharp
using System;
namespace SimpleEvent
{
// 发布器类
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);
}
}
}
在这个例子里:
EventTest 类作为发布器,它内部有一个表示数值的变量,并且定义了 NumManipulationHandler 委托以及对应的 ChangeNum 事件,通过 OnNumChanged 方法在数值变化时触发事件。
subscribEvent 类作为订阅器,定义了 printf 方法作为事件处理程序。在 Main 类的 Main 方法中完成订阅操作后,当调用 SetValue 方法改变数值并满足触发条件时,就会触发事件,进而调用订阅器中的事件处理程序输出相应内容。
示例三:锅炉系统相关事件应用
csharp
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();
}
}
}
在这个与锅炉系统相关的示例中:
- Boiler 类用于表示锅炉的基本信息,包含温度和压力属性。
- DelegateBoilerEvent 类作为事件发布器,定义了 BoilerLogHandler 委托以及 BoilerEventLog 事件,在 LogProcess 方法中根据锅炉的温度和压力情况生成相应的备注信息,并通过 OnBoilerEventLog 方法触发事件传递相关日志信息。
- BoilerInfoLogger 类用于将信息写入日志文件,实现了 IDisposable 接口来规范资源的释放。
- RecordBoilerInfo 类作为事件订阅器,定义了 Logger 方法作为事件处理程序,在 Main 方法中进行订阅操作,使得当发布器触发事件时,一方面会在控制台输出日志信息,另一方面会将信息写入指定的日志文件中。