C#之事件

news2024/12/23 13:08:13

目录

一、发布者和订阅者

(一)概述

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

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

二、源代码组件概览

三、声明事件

事件是成员

四、订阅事件

五、触发事件

六、标准事件的用法

(一)通过扩展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.coloradmin.cn/o/784501.html

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

相关文章

分析-WinHttpReceiveResponse失败问题追踪

Windows中的WinHttp库提供了比较完善的访问HTTP资源的接口API&#xff0c;一次在使用WinHTTP爬取QQ邮箱过程中&#xff0c;WinHttpReceiveResponse的调用总是失败&#xff0c;于是对此问题进行跟踪。 开始分析QQ邮箱的HTTP交互协议时&#xff0c;用到了代理工具Fiddler&#xf…

t.einsum(‘ijk,jkl->ijl‘, [a,b])

这个东西虽然计算起来真的方便的很多&#xff0c;但是对于人的理解难度是真的加大的&#xff0c;特别是高纬度的时候&#xff0c;例如&#xff1a;t.einsum(‘ijk,jkl->ijl’, [a,b])三维计算的时候。因此&#xff0c;最好的方法就是举个例子并且换一种方式来实现相同的功能…

安卓开发--4步实现Menu菜单动态显示隐藏

MenuInflater用法_韦_恩的博客-CSDN博客MenuInflater是用来加载menu布局文件的.应用程序运行时会预先加载资源中的布局文件&#xff0c;如果Menu布局中的资源比较多&#xff0c;会影响性能&#xff0c;所以可以选择MenuInflater方式用的时候加载&#xff0c;这样减轻了应用程序…

C语言通讯录

在本博客中&#xff0c;我们将介绍如何使用C语言构建一个基本的通讯录。主要涉及C语言的指针、结构体、动态内存管理、文件操作等方面的知识。我们还将学习如何使用C语言的各种功能和技巧来实现通讯录的各种操作&#xff0c;如添加联系人、编辑联系人、删除联系人和搜索联系人等…

并发与并行的区别(详细介绍)

并发和并行的区别为&#xff1a;意思不同、侧重不同、处理不同。 一、意思不同 1、并发&#xff1a;并发是指两个或多个事件在同一时间间隔发生&#xff0c;把任务在不同的时间点交给处理器进行处理。在同一时间点&#xff0c;任务并不会同时运行。 2、并行&#xff1a;并行…

【uniapp】更改富文本编辑器图片大小

代码块 //<view v-html"productDetails"></view><rich-text :nodes"productDetails"></rich-text>// 假设htmlContent字段是后台返回的富文本字段var htmlContent res.result.productDetailsconst regex new RegExp(<img, gi…

macOS Big Sur 11.7.9 (20G1426) 正式版 ISO、PKG、DMG、IPSW 下载

macOS Big Sur 11.7.9 (20G1426) 正式版 ISO、PKG、DMG、IPSW 下载 本站下载的 macOS 软件包&#xff0c;既可以拖拽到 Applications&#xff08;应用程序&#xff09;下直接安装&#xff0c;也可以制作启动 U 盘安装&#xff0c;或者在虚拟机中启动安装。另外也支持在 Window…

nginx怎么做负载均衡

Nginx怎么做负载均衡 Nginx 是一个高性能的开源反向代理服务器&#xff0c;可以用于实现负载均衡。负载均衡指的是将用户请求平均分配给多个服务器&#xff0c;以提高整体系统性能和可靠性。下面是一个详细介绍如何使用 Nginx 实现负载均衡的步骤&#xff1a; 步骤 1&#xf…

vue项目打包成App

地址一 地址二 一、将项目开发完成后&#xff0c;在vue.config.js 文件中添加路径 publicPath:‘./’ 在router/index.js关闭路由的history模式&#xff08;默认哈希&#xff09; 二、npm run build&#xff0c;生成的dist文件目录 三、打开 HBuilder X 开发工具 新建 >…

线性代数(基础篇):第一章:行列式 、第二章:矩阵

文章目录 线性代数0&#xff1a;串联各章等价条件 第1章 行列式1.行列式的定义(1)行列式的本质定义(2)行列式的逆序数法定义(3)行列式的展开定理 (第三种定义) 2.行列式的性质3.行列式的公式4.基本行列式(1)主对角线行列式(2)副对角线行列式(3)拉普拉斯行列式(4)范德蒙德行列式…

SpringBoot项目——springboot配置Tomcat两个端口,https和http的方式 jar的打包和运行

目录 引出springboot配置Tomcat两个端口&#xff0c;https和http的方式1.生成SSL证书2.配置client.p12和https端口3.配置http的8080端口WebServerFactoryCustomizer接口4.启动项目 项目应用&#xff1a;在某项目中有一个功能需要https协议Tomcat启动https和http两个端口根据htt…

深度学习中标量,向量,矩阵和张量

1.标量(Scalar) 只有大小没有方向&#xff0c;可用实数表示的一个量 2.向量(Vector) 可以表示大小和方向的量 3.矩阵(Matrix) m行n列,矩阵中的元素可以是数字也可以是符号&#xff0c;在深度学习中一般是二维数组 4.张量(Tensor) 用来表示一些向量、标量和其他张量之间的…

Vue3使用Pinia-store选项式api和组合式api两种使用方式-快速入门demo

Pinia官方文档 选项式api /** Author: Jackie* Date: 2023-06-25 09:58:10* LastEditTime: 2023-07-24 17:32:25* LastEditors: Jackie* Description: pinia* FilePath: /vue3-demo-pinia/src/store/counter.js* version:*/ import { defineStore, storeToRefs } from pinia;…

el-table 表头设置渐变色

<el-table :data"tableData" stripe><el-table-column prop"name" label"测试" align"left"></el-table-column><el-table-column prop"code" label"测试1" align"left"></…

【《Go编程进阶实战:开发命令行应用、HTTP应用和gRPC应用》——指导你使用Go语言构建健壮的、生产级别的应用程序】

谷歌在2009年发布了Go编程语言&#xff0c;并于2012年发布了1.0版。Go语言具有强大的兼容性&#xff0c;一直用于编写可扩展的重量级程序(命令行应用程序、关键基础设施工具乃至大规模分布式系统)。凭借简单性、丰富的标准库和蓬勃发展的第三方软件包生态系统&#xff0c;Go语言…

渗透D1---基础知识回顾

端口&#xff1a; http 80 https 443 ftp 20 21 telnet 23 ssh 22 DNS 53 DHCP 67 68 mail smtp 25 pop3 110 ladp 389 域控制器 3306 mysql 关系型 sqlserver 1433 c# oracle 1521 3389 windows 远程连接端口 redis nosql 6379 编码介绍&#xff1a; 主要有两…

Git实现同一个项目多个版本

需求&#xff1a; 最近项目有这样一个需求&#xff0c;就是同一个项目要求给不同的两个客户&#xff0c;这两个客户需要的功能和界面不一样但基础功能一样&#xff0c;然后修改基础功能时这两个项目的基础功能要同时修改。避免同样的代码在两个项目上各自再开发一遍。 解决办…

前端JS识别二维码内容

原文&#xff1a;https://www.cnblogs.com/houxianzhou/p/15030351.html <!DOCTYPE html> <html><head><meta charset"utf-8" /><title>图片二维码识别</title><script src"https://cdn.bootcss.com/jquery/3.4.1/jque…

如何在3ds max中创建可用于真人场景的巨型机器人:第 5 部分

推荐&#xff1a; NSDT场景编辑器助你快速搭建可二次开发的3D应用场景 1. After Effects 中的项目设置 步骤 1 打开“后效”。 打开后效果 步骤 2 我有真人版 我在After Effects中导入的素材。这是将 用作与机器人动画合成的背景素材。 实景镜头 步骤 3 有背景 选定的素材…

halcon微积分原理生成卡尺,异形产品宽度测量

1.普通测量项目中&#xff0c;我们可以利用halcon的测量模型&#xff0c;例如add_metrology_object_line_measure。很方便的测量直线&#xff0c;圆&#xff0c;椭圆&#xff0c;矩形等。这些工具都有一个缺点是&#xff0c;需要提前绘制测量位置&#xff0c;然后利用仿射变换跟…