C#之事件

目录

一、发布者和订阅者

(一)概述

(二)有关事件的重要事项

(三)有关事件的私有委托需要了解的重要事项

二、源代码组件概览

三、声明事件

事件是成员

四、订阅事件

五、触发事件

六、标准事件的用法

(一)通过扩展EventArgs来传递数据

(二)移除事件处理程序

七、事件访问器


事件基于委托,为委托提供了一种发布/订阅机制。在架构内到处都能看到事件。在Windows应用程序中,Button类提供了Click事件。这类事件就是委托。触发Click事件时调用的处理程序方法需要定义,其参数由委托类型定义。

一、发布者和订阅者

(一)概述

很多程序都有一个共同的需求,即当一个特定的程序事件发生时,程序的其他部分可以得到该事件已经发生的通知。

发布者/订阅者模式可以满足这种需求。在这种模式中,发布者类定义了一系列程序的其他部分可能感兴趣的事件。其他类可以“注册”,以便在这些事件发生时收到发布者的通知。这些订阅者类通过向发布者提供一个方法来“注册”以获取通知。当事件发生时,发布者“触发事件”,然后执行订阅者提交的所有事件。

由订阅者提供的方法称为回调方法。因为发布者通过执行这些方法来“往回调用订阅者的方法”。还可以将它们称为事件处理程序,因为它们是为处理事件而调用的代码。

 

 

(二)有关事件的重要事项

发布者(publisher):发布某个事件的类或结构,其他类可以在该事件发生时得到通知。

订阅者(subscriber):注册并在事件发生是得到通知的类或结构。

事件处理程序(event handler):由订阅者注册到事件的方法,在发布者触发事件时执行。事件处理程序方法可以定义在事件所在的类或结构中,也可以定义在不同的类或结构中。

触发(raise)事件:调用或触发事件的术语。当事件被触发时,所有注册到它的方法都会被依次调用。

(三)有关事件的私有委托需要了解的重要事项

事件的很多部分都与委托类似。实际上,事件就像是专门用于某种特殊用途的简单委托。委托和事件的行为之所以相似,是有充分理由的。事件包含了一个私有的委托。

 

  • 事件提供了对它的私有控制委托的结构化访问。也就是说你无法直接访问委托
  • 事件中可用的操作比委托要少,对于事件我们只可以添加、删除或调用事件处理程序。
  • 事件被触发时,它调用委托来依次调用调用列表中的方法。

二、源代码组件概览

委托类型声明:事件和事件处理程序必须有共同的签名和返回类型,它们通过委托类型进行描述。

事件处理程序声明:订阅者类中会在事件触发时执行的方法声明。它们不一定是显式命名的方法,还可以是匿名方法和Lambda表达式。

事件声明:发布者类必须声明一个订阅者类可以注册的事件成员。当类声明的事件为public时,称为发布了事件。

事件注册:订阅者必须注册事件才能在事件被触发时得到通知,这是将事件处理程序与事件相连的代码。

触发事件的代码:发布者类中“触发”事件并导致调用注册的所有事件处理程序的代码。

三、声明事件

发布者类必须提供事件对象。创建事件比较简单——只需要委托类型和名称。事件声明的语法如下的代码所示。代码中声明了一个叫做CountADozen的事件。注意如下有关CountADozen事件的内容。

  • 事件声明在一个类中。
  • 它需要委托类型的名称,任何附加到事件(如注册)的处理程序都必须与委托类型的签名和返回类型匹配
  • 它声明为public,这样其他类和结构可以在它上面注册事件处理程序。
  • 不能使用对象创建表达式(new 表达式)来创建它的对象
class Incrementer
{public event EventHandler CountedADozen;//event:关键字//EventHandler:委托类型//CountedADozen:事件名
}

我们可以通过使用逗号分隔的列表在一个声明语句中声明一个以上的事件。

事件是成员

一个常见的误解是把事件视为类型,然而它不是。和方法、属性一样。事件是类或结构的成员,这一点引出了几个重要的特性。

(1)由于事件是成员:

  • 我们不能在一段可执行代码中声明事件;
  • 它必须声明在类或结构中,和其他成员一样

(2)事件成员被隐式自动初始化为null。

事件声明需要委托类型的名称,我们可以声明一个委托类型或使用已有的委托类型。如果声明一个委托类型,他必须指定将被事件注册的方法的签名和返回类型。

四、订阅事件

订阅者向事件添加事件处理程序。对于一个要添加到事件的事件处理程序来说,它必须具有与事件的委托相同的返回类型和签名。

(1)使用+=运算符来为事件添加事件处理程序。事件处理程序位于该运算符的右边。

(2)事件处理程序的规范可以是以下任意一种:

  • 实例方法的名称
  • 静态方法的名称
  • 匿名方法
  • Lambda表达式

例如,下面的代码为CountADozen事件添加了3个方法:实例方法,静态方法和使用委托形式的实例方法。

incrementer.CountedADozen += IncrementDozensCount;//方法引用形式
incrementer.CountedADozen += ClassB.CounterHandlerB;//方法引用形式
mc.CountedADozen += new EventHandler(cc.CounterHandlerC);//委托形式//incrementer:类
//CountedADozen:事件成员
//IncrementDozensCount:实例方法
//ClassB.CounterHandlerB:静态方法

和委托一样,我们可以使用匿名方法和Lambda表达式来添加事件处理程序。例如,如下代码先使用Lambda表达式然后使用了匿名方法。

//Lambda表达式
incrementer.CountADozen += () => DozensCount++;//匿名方法
Incrementer.CountADozen += delegate {DozensCount++;};

五、触发事件

事件成员本身只是保存了需要被调用的事件处理程序。如果事件没有被触发,什么都不会发生。我们需要确保有代码在合适的时候做这件事情。

例如,如下代码触发了CountADozen事件。注意如下有关代码的事项。

(1)在触发事件之前和null进行比较,从而查看事件是否包含事件处理程序。如果事件是null,则表示没有事件处理程序,不能执行。

(2)触发事件的语法和调用方法一样:

  • 使用事件名称,后面跟着参数列表(包含在圆括号中)
  • 参数列表必须与事件的委托类型相匹配。

把事件声明和触发事件的代码放到一起便有了如下的发布者类声明。这段代码包含了两个成员:事件和一个叫作DoCount的方法,该方法将在适当的时候触发该事件。

class Incrementer
{public event EventHandler CountADozen;//声明事件void DoCount(object source, EventArgs args){for(int i=1;i < 100; i++)if(i % 12 == 0)if(CountADozen != null)//确认有方法可以执行CountADozen(source,args);//触发事件}
}

下面展示整个程序,包含发布者类Incrementer和订阅者类Dozens。代码中需要注意的地方如下:

  • 在构造函数中,Dozens类订阅事件,将IncrementDozensCount作为事件处理程序;
  • 在Incrementer类的DoCount方法中,每增加12个计数就触发CountADozen事件。
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();        //每增加12个计数触发事件一次}
}//订阅者
class Dozens
{public int DozensCount{get; private set;}public Dozens(Incrementer incrementer){DozensCount = 0;incrementer.CountedADozen += IncrementDozensCount;//订阅事件}//声明事件处理程序void IncrementDozensCount(){DozensCount++;}
}class Program
{static void Main(){Incrementer incrementer = new Incrementer();Dozens dozenCounter = new Dozens(incrementer);incrementer.DoCount();Console.WriteLine("Number of dozens = {0}", dozensCounter.DozensCOunt);}
}

六、标准事件的用法

GUI编程是事件驱动的,也就是说在程序运行时,它可以在任何时候被时间打断,比如按钮点击、按下按键或系统定时器。在这些情况发生时,程序需要处理事件然后继续做其他事件。

显然,程序事件的异步处理是使用C#事件的绝佳场景。Windows GUI编程如此广泛地使用了事件,以至于对于事件的使用,.NET框架提供了一个标准模式。该标准模式的基础就是System命名空间中声明的EventHandler委托类型。EventHandler委托类型的声明如下:

public delegate void Eventhandler(object sender, EventArgs e);

关于该声明需要注意以下几点:

  • 第一个参数用来保存触发事件的对象的引用。由于它是object类型的,所以可以匹配任何类型的实例。
  • 第二个参数用来保存状态信息,指明什么类型适用于该应用程序。
  • 返回类型是void

(一)通过扩展EventArgs来传递数据

为了向自己的事件处理程序的第二个参数传入数据,同时遵循标准惯例,我们需要声明一个派生自EventArgs的自定义类,它可以保存我们需要传入的数据。类的名称应该以EventArgs结尾。

例如,如下代码声明了一个自定义类,他能将字符串存储在名为Message的字段中。

public class IncrementerEventArgs:EventArgs
{public int IterationCount{get;set}//存储一个整数
}

现在我们有了一个自定义的类,可以向事件处理程序的第二个参数传递数据,所以你需要一个使用新自定义类的委托类型。为此,可以使用泛型版本的委托Eventhandler<>。

要使用泛型委托需要做到以下两点:

  • 将自定义类的名称放在尖括号内
  • 在需要使用自定义委托类型的地方使用整个字符串。例如,event声明可能为如下形式:
public event EventHandler<IncrementerEventArgs> CounteDozen;//EventHandler<IncrementerEventArgs>   泛型委托使用自定义类
//CounteDozen   事件名称

(二)移除事件处理程序

再用完事件处理程序之后,可以从事件中把它移除。可以利用 -= 运算符事件处理程序从事件中移除。

p.SImpleEvent -= s.MethodB;   //移除事件处理程序MethodB

下面的代码向SimpleEvent事件添加了两个处理程序,然后触发事件。每个处理程序都将被调用并打印文本行。然后将MethodB处理程序从事件中移除。当事件再次被触发时,只有MethodA处理程序会打印一行。

class Publisher
{public event EventHandler SimpleEvent;public void RaiseTheEvent() {SimpleEvent(this,null);}
}class Subscriber
{public void MethodA(object o, EventArgs e) {Console.WriteLine("AAA");}public void MethodB(object o, EventArgs e) {Console.WriteLine("BBB");}
}class Program
{static void Main(){Publisher p = new Publisher();Subscriber p = new Subscriber();p.SimpleEvent += s.MethodA;p.SimpleEvent += s.MethodB;p.RaiseTheEvent();Console.WriteLine("\r\nRemove MethodB");p.SimpleEvent -= s.MethodB;p.RaiseTheEvent();}
}

这段代码会产生如下输出:

AAA

BBB

Remove MethodB

AAA

如果一个处理程序向事件注册了多次,那么当执行命令移除处理程序时,将只移除列表中该处理程序的最后一个实例。

七、事件访问器

之前提过,事件只能使用 += 和 -= 运算符。这两个运算符有良好的行为。

然而,我们可以修改这两个运算符的行为,在使用它们时让事件执行任何我们希望执行的自定义代码。

要改变这两个运算符的操作,必须为事件定义事件访问器。

  • 有两个访问器:add和remove
  • 声明事件的访问器看上去和声明一个属性差不多。

下面的示例演示了具有访问器的事件声明。两个访问器都有叫作value的隐式值参数,它接受实例或静态方法的引用。

public event EventHandler CountedADozen
{add{...                    //执行+=运算符的代码}remove{...                    //执行-=运算符的代码}
}

声明了事件访问器之后,事件不包含任何内嵌委托对象。我们必须实现自己的机制来存储和移除事件注册的方法。

事件访问器表现为void方法,也就是不能使用返回值的return语句。

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

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

相关文章

【Zerotier】通过docker自建PLANET服务器

在如今全球互联的时代&#xff0c;我们对于互联网的依赖程度越来越高。然而&#xff0c;传统的网络连接方式在某些情况下可能会受到一些限制&#xff0c;例如局域网的范围限制、防火墙的阻断或者设备所处的多层NAT等。但是&#xff0c;现在有一个名为ZeroTier的工具出现了&…

【C语言进阶】文件操作

&#x1f525;博客主页&#xff1a;小王又困了 &#x1f4da;系列专栏&#xff1a;C语言 &#x1f31f;人之为学&#xff0c;不日近则日退 ❤️感谢大家点赞&#x1f44d;收藏⭐评论✍️ 目录 一、什么是文件 1.1程序文件 1.2数据文件 1.3文件名 二、文件的打开和关闭 2…

银河麒麟服务器v10 sp1 nginx 部署项目

上一篇&#xff1a;银河麒麟服务器v10 sp1 nginx开机自动启动_csdn_aspnet的博客-CSDN博客 由于项目为前后端分离&#xff0c;前端项目使用nginx部署&#xff0c;VUE项目打包后上传至银河麒麟服务器&#xff1a; 8063 为前端项目文件目录&#xff0c;修改配置 &#xff0c;默认…

脑电信号处理与特征提取——三. 脑电实验设计的原理与实例(古若雷)

三、脑电实验设计的原理与实例 被试间设计的实验结果也有可能是人员不同造成的&#xff0c;所以建议被试内设计。

双端队列(deque)与优先队列(priority_queue)

文章目录 一.双端队列——deque1.deque的优点与缺点2.deque的原理 二.优先队列——priority_queue1.什么是优先队列&#xff1f;2.优先队列的基本使用3.什么是仿函数&#xff1f;4.优先队列的模拟实现 一.双端队列——deque 在上一章stack、queue的模拟实现中&#xff0c;我们…

Mysql 数据库开发及企业级应用

文章目录 1、Mysql 数据库开发及企业级应用1.1、为什么要使用数据库1.1.1、数据库概念&#xff08;Database&#xff09;1.1.2、为什么需要数据库 1.2、程序员为什么要学习数据库1.3、数据库的选择1.3.1、主流数据库简介1.3.2、使用 MySQL 的优势1.3.3、版本选择 1.4、Windows …

【VUE】解决图片视频加载缓慢/首屏加载白屏的问题

1 问题描述 在 Vue3 项目中&#xff0c;有时候会出现图片视频加载缓慢、首屏加载白屏的问题 2 原因分析 通常是由以下原因导致的&#xff1a; 图片或视频格式不当&#xff1a;如果图片或视频格式选择不当&#xff0c;比如选择了无损压缩格式&#xff0c;可能会导致文件大小过大…

unity 控制text根据字数自动扩展大小,并扩展UI背景

需求&#xff1a;文字内容位置保持不变&#xff0c;向下增加&#xff0c;背景框随之同步扩展。 1.UGUI 九宫格 拉伸 对背景框图片资源处理&#xff0c;避免图片拉伸。 (10条消息) unity UGUI 九宫格 拉伸_unity九宫格拉伸_野区捕龙为宠的博客-CSDN博客 2.背景框添加组件 3.…

php裁剪图片,并给图片加上水印

本次以裁剪四个图片为例&#xff0c;图片如下 代码如下 public function cutImg($imgUrl){try{// 读取原始图片$src_img imagecreatefromjpeg($imgUrl);// 获取原始图片的宽度和高度$src_width imagesx($src_img);$src_height imagesy($src_img);// 计算每个部分的宽度和高…

【数字信号处理】带通采样定理及其MATLAB仿真

目录 一、带通采样定理1.1 内容1.2 公式推导 二、MATLAB信号仿真2.1 信号仿真实验2.2 MATLAB代码 三、总结参考 一、带通采样定理 按照奈奎斯特采样定理(低通采样)&#xff0c;采样频率 f s f_{s} fs​ 要大于等于信号中最高频率 f m a x f_{max} fmax​ 的2倍&#xff0c;才…

C++OpenCV(2):图像处理基础概念与操作

&#x1f506; 文章首发于我的个人博客&#xff1a;欢迎大佬们来逛逛 &#x1f506; OpenCV项目地址及源代码&#xff1a;点击这里 文章目录 图形读取与显示加载图片显示图片打印图片信息保存图片 色彩模型转换RGB颜色模型HSV颜色模型HLS模型LAB模型 图像像素读写操作像素算数运…

macOS 源码编译 qpress

╰─➤ git clone https://github.com/PierreLvx/qpress.git ╰─➤ cd qpress ╰─➤ make g -O3 -o qpress -x c quicklz.c -x c qpress.cpp aio.cpp utilities.cpp -lpthread -Wall -Wextra -Werror ╰─➤ sudo make install …

怎么快速定位bug?怎么编写测试用例?

目录 01定位问题的重要性 02问题定位技巧 03初次怎么写用例 作为一名测试人员如果连常见的系统问题都不知道如何分析&#xff0c;频繁将前端人员问题指派给后端人员&#xff0c;后端人员问题指派给前端人员&#xff0c;那么在团队里你在开发中的地位显而易见 &#xff0c;口碑…

垃圾回收标记阶段算法

1.标记阶段的目的 主要是在GC在前&#xff0c;判断出哪些是有用的对象&#xff0c;哪些是需要回收的对象&#xff0c;只有被标记为垃圾对象&#xff0c;GC才会对其进行垃圾回收。判断对象是否为垃圾对象的两种方式&#xff1a;引用计数算法和可达性分析算法。 2.引用计数算法…

如何搭建使用dubbo-Admin?

dubbo-Admin介绍 一款用于dubbo可视化界面操作的管理平台 dubbo-Admin特点 dubbo-Admin是dubbo的管理界面平台&#xff0c;且是一个前后端分离的项目&#xff0c;前端使用vue&#xff0c;后端使用springboot。 软件下载 dubbo-admin-0.5.0.zip 软件使用

会议OA项目之会议审批(亮点功能:将审批人签名转换为电子手写签名图片)

&#x1f973;&#x1f973;Welcome Huihuis Code World ! !&#x1f973;&#x1f973; 接下来看看由辉辉所写的关于OA项目的相关操作吧 目录 &#x1f973;&#x1f973;Welcome Huihuis Code World ! !&#x1f973;&#x1f973; 一.主要功能点介绍 二.效果展示 三.前端…

MongoDB 的日常使用

一、简介 1、 常见的数据库分类 RDBMS&#xff08;关系型数据库&#xff09;&#xff1a;常见的关系型数据库有 Oracle、DB2、Microsoft SQL Server、Microsoft Access、MySQL&#xff1b; NoSQL&#xff08;非关系型数据库&#xff09;&#xff1a;常见的非关系型数据库有 …

thinkphp实现无限分类(使用递归)

thinkphp实现无限分类&#xff08;使用递归&#xff09; 本文实例为大家分享了thinkphp实现无限分类的详细代码&#xff0c;希望对大家学习无限分类有所启发。 数据库&#xff1a;test 数据表&#xff1a;&#xff08;tp_category&#xff09;&#xff1a; Common/conf/conf…

在VSCode中实现Rust编程调试指南

在 VS Code 中调试 Rust&#xff1a;终极指南 在本教程中&#xff0c;您将学习如何使用 VS Code 调试 Rust。可用于使用 VS Code 调试 Rust 的操作。设置 VS Code 来调试 Rust Rust因其易用性、安全性和高性能而继续保持其作为最受欢迎的编程语言的地位。随着 Rust 的流行&…

elementui el-table折叠表格,点击主表数据展开从表明细

用element-ui 的el-table实现&#xff1a;主表table可实现展开行显示关联的明细表table的列表数据&#xff0c;效果图如下 <el-tableref"tableData"v-loading"listLoading":data"tableData"row-key"id"borderstripehighlight-curr…