图解C#高级教程(二):事件

news2024/9/30 9:41:50

在现实生活当中,有一些事情发生时,会连带另一些事情的发生。例如,当某国的总统发生换届时,不同党派会表现出不同的行为。两者构成了“因果”关系,因为发生了A,所以发生了B。在编程语言当中,具有类似的概念。在编程语言中,发生的事情 A 称为事件,因 A 发生的事情 B 称为对事件的处理或者响应(事件处理程序)。

本章主要讲解 C# 语言当中的发布者-订阅者模式的概念、代码实现该模式时的组成部分以及标准事件的用法。

1. 发布者和订阅者的概念

在程序中,我们很多时候会面临这样一个需求:当一个特定的程序事件发生时,程序的其它部分(类、函数或者其它)可以得到该事件已经发生的通知并对此做出相应的处理。

发布者-订阅者模式(publisher/subscriber pattern)可以满足这种需求。在这种模式中,发布者类定义了订阅者类感兴趣的事件。当事件发生时,发布者通知到这些订阅者,然后订阅者执行相应的事件处理函数。

但是,发布者是如何通知到这些订阅者呢?订阅者通过注册函数。其实就是发布者维护了一个关注某个事件的订阅者集合。例如,我在 csdn 博客上开了某个领域的专栏,然后你订阅了这个专栏,在后台会维护关注这个专栏的用户列表。当我更新了这个专栏的文章时,会通知到每一个订阅的用户。

那么,订阅者又是如何在事件发生时执行相应的事件处理函数呢?订阅者向发布者注册事件发生时的事件处理函数(回调函数,我们称函数的参数类型是函数的函数)。

下图说明了发布者-订阅者模式的工作流程:
在这里插入图片描述
下面是发布者-订阅者模式的组件:

  1. 发布者:发布某个事件的类或结构。维护一个订阅者集合(可选)、一个事件处理程序的集合、提供给订阅者注册订阅和回调函数的接口;
  2. 订阅者:关注事件的类或者结构。需要向订阅者提供回调函数名;
  3. 触发事件。本质上是触发事件的代码。

在 C#高级教程(一):委托当中介绍了委托。实际上,事件就像是专门用于某种特殊用途的委托。

2. 源代码组件

为了实现发布者-订阅者模式,在代码中需要完成以下 5 部分:

  • 委托类型声明:事件和事件处理程序必须具有相同的签名和返回类型,它们通过委托类型进行描述;
  • 事件处理程序声明:订阅者类中会在事件触发时执行的方法调用;
  • 事件声明:发布者类必须声明一个订阅者类可以注册的事件成员;
  • 事件注册:订阅者必须订阅事件才能在它被触发时得到通知;
  • 触发事件的代码:发布者类中“触发”事件并执行所有事件处理程序的代码。
    在这里插入图片描述
    下面是一个简单的代码实现:
delegate void Handler();

class Incrementer
{
    public event Handler CountedADozen;     // 事件名

    public void DoCount()
    {
        for (int i = 1; i < 100; i++)
        {
            if (i % 12 == 0 && CountedADozen!= null) // 触发事件的代码
            {
                CountedADozen();        // 调用事件
            }
        }
    }
}

// 订阅者
class Dozens
{
    public int DozensCount { get; set; }

    public Dozens(Incrementer incrementer)
    {
        DozensCount = 0;
        incrementer.CountedADozen += IncrementDozenCount;   // 注册回调函数
    }
    
    // 回调函数
    void IncrementDozenCount()
    {
        DozensCount++;
    }
}

class Programer
{
    static void Main()
    {
        Incrementer incrementer = new Incrementer();
        Dozens dozens = new Dozens(incrementer);

        incrementer.DoCount();
        Console.WriteLine("Dozens count: {0}", dozens.DozensCount);
    }
}

输出:
在这里插入图片描述

3. 标准事件的用法

由于 GUI 编程是事件驱动的,而 Windows GUI 编程广泛地使用了事件,因此 .NET 框架提供了一个标准模式。具体就是在 System 命名空间提供了 EventHandler 委托类型。
在这里插入图片描述
第二个参数 EventArgs 设计为不能用来传递任何数据,它用于不需要传递数据的事件处理程序。如果你希望传递数据,必须声明一个派生自 EventArgs 的类,使用合适的字段来保存需要传递的数据。

接下来,我们就使用标准事件改写第二节的程序:

class Incrementer
{
    public event EventHandler CountedADozen;     // 事件名

    public void DoCount()
    {
        for (int i = 1; i < 100; i++)
        {
            if (i % 12 == 0 && CountedADozen!= null) // 触发事件的代码
            {
                CountedADozen(this, null);        // 调用事件
            }
        }
    }
}
// 订阅者
class Dozens
{
    public int DozensCount { get; set; }

    public Dozens(Incrementer incrementer)
    {
        DozensCount = 0;
        incrementer.CountedADozen += IncrementDozenCount;   // 注册回调函数
    }
    
    // 回调函数
    void IncrementDozenCount(object sender, EventArgs e)
    {
        DozensCount++;
    }
}

class Programer
{
    static void Main()
    {
        Incrementer incrementer = new Incrementer();
        Dozens dozens = new Dozens(incrementer);

        incrementer.DoCount();
        Console.WriteLine("Dozens count: {0}", dozens.DozensCount);
    }
}

通过扩展 EventArgs 来传递数据

为了向自己的事件处理程序的第二个参数传入数据,并且又符合标准惯例,我们需要声明一个派生自 EventArgs 的自定义类,用来保存我们需要传入的数据。

下面是改写后的代码:

// 派生自EventArgs的自定义类
public class IncrementerArgs: EventArgs
{
    public int IterationCount { get; set; }
}

class Incrementer
{
    // 使用自定义类的泛型委托
    public event EventHandler<IncrementerArgs> CountedADozen;     // 事件名

    public void DoCount()
    {
        IncrementerArgs args = new IncrementerArgs();
        for (int i = 1; i < 100; i++)
        {
            if (i % 12 == 0 && CountedADozen!= null) // 触发事件的代码
            {
                args.IterationCount = i;
                CountedADozen(this, args);        // 调用事件
            }
        }
    }
}
// 订阅者
class Dozens
{
    public int DozensCount { get; set; }

    public Dozens(Incrementer incrementer)
    {
        DozensCount = 0;
        incrementer.CountedADozen += IncrementDozenCount;   // 注册回调函数
    }
    
    // 回调函数
    void IncrementDozenCount(object sender, IncrementerArgs e)
    {
        Console.WriteLine("Incremented at iteration: {0} and {1}",
                            e.IterationCount, sender.ToString());
        DozensCount++;
    }
}

class Programer
{
    static void Main()
    {
        Incrementer incrementer = new Incrementer();
        Dozens dozens = new Dozens(incrementer);

        incrementer.DoCount();
        Console.WriteLine("Dozens count: {0}", dozens.DozensCount);
    }
}

输出:
在这里插入图片描述

移除事件处理程序

在用完了事件处理程序之后,可以从事件中把它移除。下面是一个例子:

class Publisher
{
    public event EventHandler SimpleEvent;

    public void RaiseTheEvent() { SimpleEvent(this, null); }
}

class Subsriber
{
    public void MethodA(object o, EventArgs e) { Console.WriteLine("MethodA called"); }
    public void MethodB(object o, EventArgs e) { Console.WriteLine("MethodB called"); }

}

class Program
{
    static void Main()
    {
        Publisher p = new Publisher();
        Subsriber s = new Subsriber();

        p.SimpleEvent += s.MethodA;
        p.SimpleEvent += s.MethodB;
        p.RaiseTheEvent();

        Console.WriteLine("\r\nRemove MethodB");
        p.SimpleEvent -= s.MethodB;
        p.RaiseTheEvent();
    }
}

程序的输出:
在这里插入图片描述

小结:本章介绍了 C# 语言当中的发布者-订阅者模式的概念,源代码组件的五个部分,以及标准事件的用法。

各位道友,码字不易。如有收获,记得一键三连。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2179303.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Motion open Heart 详细动画化开放式心脏解剖

详细和动画的心脏直视解剖。 具有真实的运动和精确的心动周期动画。 包括真实阀门动画序列。 配备高清纹理2048x2048和高清法线贴图,可在教育和游戏方面获得更好、更真实的效果。为(VR)虚拟现实场景和增强现实(AR)做好准备。 下载:​​Unity资源商店链接资源下载链接 …

高职院校“ICT工程师”人才培养方案——以华为认证高级网络工程师HCIP为例

一、引言 在电子信息领域&#xff0c;新技术、新理念和新思路的迅猛发展正推动着信息和网络技术成为各行业产业链的关键部分。信息技术与网络技术的深度融合催生了多样化的应用技术。ICT行业正逐渐渗透到生活的每一个角落&#xff0c;引领着新一轮信息通信产业的发展浪潮。 为…

在LabVIEW中如何读取EXCEL

在LabVIEW中读取Excel文件通常使用“报告生成工具包”&#xff08;Report Generation Toolkit&#xff09;。以下是详细步骤&#xff1a; ​ 安装工具包&#xff1a;确保已安装“报告生成工具包”。这通常随LabVIEW一起提供&#xff0c;但需要单独安装。 创建VI&#xff1a; 打…

一文详解:跨国医疗机构安全合规文件流转的跨境传输解决办法

跨国医疗机构是指那些能够在不同国家之间提供医疗服务的机构&#xff0c;它们通常具有国际化的医疗网络、专业的医疗团队和先进的医疗设备。这些机构不仅能够帮助患者获取国外优质的医疗资源&#xff0c;还能提供包括医疗咨询、治疗安排、病历翻译、签证办理、海外陪同等在内的…

中国联通(海外)数据中心资源:从基础设施运维服务到IDC机房增值服务

在全球化日益加深的今天&#xff0c;企业海外拓展已成为其发展战略的重要一环。然而&#xff0c;面对复杂多变的国际环境和严格的业务要求&#xff0c;如何确保海外业务的高效运行与数据安全&#xff0c;成为了企业亟需解决的关键问题。中国联通国际有限公司凭借其丰富的全球资…

实验5 预备实验1-交换机端口的基本配置

交换机端口的基本配置 &#xff08;本预备实验的重点是四种操作模式的切换&#xff09; 一、实验目的 1、练习cisco 交换机端口的基本配置 二、实验内容和结果 1、新建交换机 2、点击交换机图标&#xff0c;进入CLI窗口&#xff0c;可以看到交换机加电后会进行加电自检&…

[uni-app]小兔鲜-04推荐+分类+详情

热门推荐 新建热门推荐组件, 动态设置组件的标题 <template><!-- 推荐专区 --><view class"panel hot"><view class"item" v-for"item in list" :key"item.id">... ...<navigator hover-class"none&…

在 C++ std::set 中如何利用不同类型的值进行搜索?

在 C 集合中如何利用不同类型的值进行搜索 一、背景二、初衷三、is_transparent四、总结 一、背景 C14 引入了一项引人注目的功能&#xff0c;为关联容器弥补了某些案例中长久以来的不足之处&#xff1a;即允许使用在语义上被视为键&#xff0c;但在技术上并非真正键的值对关联…

VS code Jupyter notebook 导入文件目录问题

VS code Jupyter notebook 导入文件目录问题 引言正文引言 这几天被 VS code 中 Jupyter Notebook 中的文件导入折磨的死去活来。这里特来说明一下放置于不同文件夹下的模块该如何被导入。 正文 首先,我们需要按下 Ctrl + , 键打开设置,然后搜索 notebook file root。在如…

window java17改成java 8

window上装了两个版本的Java&#xff0c;目前全局生效的是Java17&#xff0c;想切换成java8的全局。但是在修改环境变量的Path之后&#xff0c;java -version 还是java 17 但是自己的JAVA_HOME 和Path 都没配错啊… 怕是%JAVA_HOME%\bin\ 有问题&#xff0c;我还特意重写了bin…

【GEE学习第二期】GEE数据类型、语法

【GEE学习第二期】GEE数据类型、语法 GEE基本数据类型基本语法循环条件判断累加 可视化影像与波段影像集数据导出数值与绘图保存影像 参考 GEE基本数据类型 GEE 中使用的主要数据类型是栅格&#xff0c;涵盖从本地到全球范围的图像&#xff0c;可从数百个卫星和航空资源获得图…

网盘能否作为FTP替代产品?企业该如何进行FTP国产化替代?

近年来&#xff0c;信创的概念引入和高效实践落地让更多的行业企业自发性地进行国产化替代&#xff0c;目前信创国产化替代还多发生在操作系统和应用层面&#xff0c;软件工具等目前还在下一阶段规划&#xff0c;但很多企业未雨绸缪&#xff0c;已经在做调研和尝试。 FTP作为世…

数据结构:并查集

数据结构&#xff1a;并查集 并查集原理实现框架初始化合并查询获取成员路径压缩其它 总代码 并查集 在生活中&#xff0c;经常会出现分组问题。比如一个班级分为多个小组&#xff0c;打篮球分为两方等等。在同一个组中的所有成员&#xff0c;就构成一个集合。对这种一个群体分…

基于SSM的会员管理系统【附源码】

基于SSM的会员管理系统&#xff08;源码L文说明文档&#xff09; 目录 4 系统设计 4.1 系统概述 4.2 数据库设计原则 4.3 数据表 第五章 系统实现 5.1用户功能模块 5.2管理员功能模块 5.3前台首页功能模块 4 系统…

可视化是工业互联网的核心技术之一,都有哪些应用场景?

一、工业互联网是什么&#xff0c;发展的来胧去脉 工业互联网是指利用互联网技术和物联网技术&#xff0c;将工业生产中的各种设备、机器、传感器等进行互联互通&#xff0c;实现信息的实时采集、传输和分析&#xff0c;从而实现生产过程的智能化、自动化和高效化。 工业互联网…

微信网页 上传图片压缩

微信网页上传图片时的压缩问题可以通过多种方法解决。以下是一些有效的方案和相关API的使用说明。 主要解决方案 1. 使用Canvas进行自定义压缩: 对于需要适配多种设备和格式的情况,可以利用Canvas API进行图片重绘和压缩。通过获取图片信息、设置Canvas尺寸、绘制图片并…

地图资源下载工具(geodatatool)下载 亚洲 8 米 DEM数据

本数据集提供的 DEM 镶嵌图是由 DigitalGlobe 卫星的超高分辨率 (VHR) 沿轨和跨轨立体图像生成的。为了生成 DEM 镶嵌图块&#xff0c;超过 4000 个 DEM 条带与加权平均 镶嵌程序合并&#xff0c;以减少错误并消除接缝。镶嵌图块为 100 公里 x 100 公里&#xff0c;8 米处为 …

【easypoi 一对多导入解决方案】

easypoi 一对多导入解决方案 1.需求2.复现问题2.1校验时获取不到一对多中多的完整数据2.2控制台报错 Cannot add merged region B5:B7 to sheet because it overlaps with an existing merged region (B3:B5). 3.如何解决第二个问题处理&#xff1a; Cannot add merged region …

tr命令:替换文本中的字符

一、命令简介 ​tr​ 命令用于转换或删除文件中的字符。它可以从标准输入中读取数据&#xff0c;对数据进行字符替换、删除或压缩&#xff0c;并将结果输出到标准输出。 ‍ 二、命令参数 格式 tr [选项] [集合1] [集合2]选项和参数 ​ ​-c​​: 指定 集合 1 的补集。​ …

Vulhub zico 2靶机详解

项目地址 https://download.vulnhub.com/zico/zico2.ova实验过程 将下载好的靶机导入到VMware中&#xff0c;设置网络模式为NAT模式&#xff0c;然后开启靶机虚拟机 使用nmap进行主机发现&#xff0c;获取靶机IP地址 nmap 192.168.47.1-254根据对比可知Zico 2的一个ip地址为…