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