C# 事件(Event)

文章目录


前言

在 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 方法中进行订阅操作,使得当发布器触发事件时,一方面会在控制台输出日志信息,另一方面会将信息写入指定的日志文件中。
相关推荐
Yhame.2 分钟前
Java 集合框架中的 List、ArrayList 和 泛型 实例
java
coding侠客3 分钟前
Spring Boot 多数据源解决方案:dynamic-datasource-spring-boot-starter 的奥秘
java·spring boot·后端
委婉待续8 分钟前
java抽奖系统(八)
java·开发语言·状态模式
qq_3975623121 分钟前
android studio更改应用图片,和应用名字。
android·ide·android studio
峥嵘life23 分钟前
Android Studio版本升级那些事
android·ide·android studio
新手上路狂踩坑25 分钟前
Android Studio的笔记--BusyBox相关
android·linux·笔记·android studio·busybox
weixin_5375904543 分钟前
《Java编程入门官方教程》第八章练习答案
java·开发语言·servlet
CodeClimb1 小时前
【华为OD-E卷-最左侧冗余覆盖子串 100分(python、java、c++、js、c)】
java·python·华为od
pchmi1 小时前
C# OpenCV机器视觉:模板匹配
opencv·c#·机器视觉