(七)CSharp-CSharp图解教程版-事件

一、发布者和订阅者

发布者/订阅者模式(publish/subscriber pattern): 很多程序都有一个共同的需求,即当一个特定的程序事件发生时,程序的其他部分可以得到该事件已经发生的通知。

发布者:

  • 发布者类定义了一系列程序的其他部分可能感兴趣的事件。
  • 发布某个事件的类或结构,其他类可以在该事件发生时得到通知。

订阅者:

  • 订阅者类可以“注册”,以便在这些事件发生时收到发布者的通知。这些订阅者类通过向发布者提供一个方法来“注册”以获取通知。
  • 注册并在事件发生时得到通知的类或结构。

事件:

  • 当事件发生时,发布者“触发事件”,然后执行订阅者提交的所有事件。
  • 调用(invoke)或触发(fire)事件的术语。当事件被触发时,所有注册到它的方法都会被依次调用。

事件是一种特殊的多播委托。(术语定义来源:Microsoft 开发文档:事件)
事件是类或结构的成员。

事件处理程序:

  • 回调方法。由订阅者提供的方法称为回调方法,因为发布者通过执行这些方法来“往回调用订阅者的方法”。它们是为处理事件而调用的代码。
  • 由订阅者注册到事件的行为,在发布者出发事件时执行。

请添加图片描述

事件包含了一个私有的委托。

请添加图片描述

有关事件的私有委托:

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

图15-3演示:

请添加图片描述

  • Incrementer 定义了一个 CountedADozen 事件。
  • 订阅者类 Dozens 和 SomeItherClass 各有一个注册到 CountedADozen 事件的事件处理程序。
  • 每当触发事件时,都会调用这些处理程序。

二、源代码组件概览

源代码组件:

  • 委托类型声明: 事件和事件处理程序必须有共同的签名和返回类型,它们通过委托类型进行描述。
  • 事件处理程序声明: 订阅者类中会在事件触发时执行的方法声明。它们不一定是显式命名的方法,还可以是匿名方法或 Lambda 表达式。
  • 事件声明: 发布者类必须声明一个订阅者可以注册的事件成员。当类声明的事件为 public 时,称为发布了事件。
  • 事件注册: 订阅者必须注册事件才能在事件被触发时得到通知。这是将事件处理程序与事件相连的代码。
  • 触发事件的代码: 发布者类中的”触发“事件并导致调用注册的所有事件处理器的代码。

请添加图片描述

三、声明事件

public event EventHandler CountedADpzen;

//声明多个事件
public event EventHandler MyEvent1,MyEvent2,OtherEvent;

//静态事件
public static event EventHandler CountedADozen;

事件是类或结构的成员。
由于事件是成员:

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

事件成员被隐式自动初始化为 null;

四、订阅事件

订阅者向事件添加事件处理程序。

  • 使用 += 运算符来为事件添加事件处理程序。

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

    • 实例方法的名称;
    • 静态方法的名称;
    • 匿名方法;
    • Lambda 表达式。
```c#
class Incrementer
{
public event EventHandler CountedADpzen;
}

class ClassB
{
public static CounterHnadlerb(){}
}

class ClassC
{
public static CounterHnadlerC(){}
}

class Pargam
{
static void Main()
{
Incrementer incrementer = new Incrementer();
//添加实例方法
incrementer.CountedADozen += IncrementDzensCount;
//添加静态方法
incrementer.CountedADozen += ClassB.CounterHnadlerb;

ClassC cc = new ClassC();
//以委托形式添加实例方法
incrementer.CountedADozen += new EventHandler(cc.CounterHandlerC);

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

}

五、触发事件

if(CountedADozen != null)
{
CountedADozen(source,args)
}

//CountedADozen:事件名称
//source,args:参数列表

整个程序的代码:

    //1、声明委托
    delegate void Handler();

    //发布者
    class Incrementer
    {
        //2、创建事件并发布
        public event Handler CountedADozen;

        public void DoCount()
        {
            for(int i = 1; i < 100; i++)
            {
                if(i %12 ==0 && CountedADozen != null)
                {
                    //3、每增加12个计数触发事件一次
                    CountedADozen();
                }
            }
        }
    }

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

        public Dozens(Incrementer incrementer)
        {
            DozensCount = 0;
            //5、订阅事件
            incrementer.CountedADozen += IncrementDozensCount;
        }

        //4、声明事件处理程序
        void IncrementDozensCount()
        {
            DozensCount++;
        }
    }

    class Program
    {
        
        static void Main(string[] args)
        {
            Incrementer incrementer = new Incrementer();
            Dozens dozensCounter = new Dozens(incrementer);

            incrementer.DoCount();
            Console.WriteLine("Number of dozens = {0}", dozensCounter.DozensCount);
            Console.ReadKey();
        }
    }

输出结果:

Number of dozens = 8

六、标准事件的用法

在程序需要处理事件然后继续作其他事情时,就要对程序事件进行异步处理。Windows GUI 编程广泛使用例如事件。

对事件的使用,.NET 框架提供了一个标准模式,Ssytem命名空间中声明的 EventHandler 委托类型。

EventHandler的声明:

  • 第一个参数,用来保存触发事件的对象的引用。
  • 第二参数用来保存状态信息,指明什么类型适用于该应用程序。
  • 返回类型是 void。
public delegate void EventHandler(object sender,EventArgs e);

EventArgs 参数的作用:

  • EventArgs 不能传递任何数据。它用于不需要传递数据的事件处理程序——通常会被忽略。(比如EventArgs 可以传递状态:左键鼠标事件的是释放状态还是按下状态)
  • 如果你希望传递数据,必须声明一个派生自 EventArgs 的类,并使用合适的字段来保存需要传递的数据。

七、通过扩展 EventArgs 来传递数据

为了能够通过事件参数的 EventArgs 来传递数据,我们需要声明一个派生自 EventArgs 的自定义类。

public class IncrementerEventArgs : EventArgs
{
public int IterationCount{get;set;}
}

实现代码:

 //自定义EventArgs
    public class IncrementerEventArgs : EventArgs
    {
        public int IterationCount { get; set; }
    }

    //发送者
    public class Incrementer
    {
        public event EventHandler<IncrementerEventArgs> CountedDozen;

        public void DoCount()
        {
            IncrementerEventArgs args = new IncrementerEventArgs();
            for(int i = 1; i < 100;i++)
            {
                if(i % 12 == 0 && CountedDozen != null)
                {
                    args.IterationCount = i;
                    CountedDozen(this, args);
                }
            }
        }
    }

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

        public Dozens(Incrementer incrementer)
        {
            DozensCount = 0;
            incrementer.CountedDozen += IncrementDozensCount;
        }

        void IncrementDozensCount(object source, IncrementerEventArgs e)
        {
            Console.WriteLine($"Incremented at iteration:{ e.IterationCount } in { source.ToString() }");
            DozensCount++;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Incrementer incrementer = new Incrementer();
            Dozens dozensCounter = new Dozens(incrementer);

            incrementer.DoCount();
            Console.WriteLine($"Number of dozens = { dozensCounter.DozensCount }");
            Console.ReadKey();
        }
    }

输出结果:

Incremented at iteration:12 in ConsoleApplication2.Incrementer
Incremented at iteration:24 in ConsoleApplication2.Incrementer
Incremented at iteration:36 in ConsoleApplication2.Incrementer
Incremented at iteration:48 in ConsoleApplication2.Incrementer
Incremented at iteration:60 in ConsoleApplication2.Incrementer
Incremented at iteration:72 in ConsoleApplication2.Incrementer
Incremented at iteration:84 in ConsoleApplication2.Incrementer
Incremented at iteration:96 in ConsoleApplication2.Incrementer
Number of dozens = 8

八、移除事件处理程序

incrementer.CountedDozen -= IncrementDozensCount;

如果一个处理程序向事件注册了多次,那么当执行命令移除处理程序时,将只移除列表中该处理程序的最后一个实例。(如果一个处理程序多次重复了注册事件,移除时,只移除最后一个相同的处理程序实例,而其他相同的事件处理程序仍然可被回调。)

九、事件访问器

一般情况下,事件只能许 += 和 -= 运算符。但是我们可以修改这两个运算符的行为,在使用它们时让事件执行任何我们希望执行的自定义代码。

事件访问器: 为了改变这两个运算符的操作而定义。

  • 有两个访问器:add 和 remove。
  • 声明事件的访问器看上去和声明一个属性差不多。
public event EventHandler CountedADozen
{
add{...} //执行 +=
remove{...} //执行 -=
}
  • 声明了事件访问器之后,事件不包含任何内嵌委托对象。我们必须实现自己的机制来存储和移除事件注册的方法。(就是说在事件访问器里来编写“存储和移除事件注册的方法”的其他代码逻辑)

  • 事件访问器表现为 void 方法,也就是不能使用返回值的 return 语句。(此处跟属性有不同的是,属性 get 是有对应类型的返回值的。而事件访问器 get 和 set 都有 value。)

书上没有提供事件访问器的代码例子,但我们也不能只学理论而不亲自动手去实现这个代码逻辑吧。

所以还是要动手实现一下事件访问器的代码例子(模仿按钮触发事件的例子):

根据已学知的识点,以下有使用到:

  • 扩展 EventArgs:用来描述点击按钮后鼠标的行为状态,比如鼠标被按下或被释放的状态。
  • 测试代码例子之后,关于委托与事件之间的关系。

1、自定义鼠标行为状态和鼠标 EventArgs 事件参数:

  //枚举鼠标的行为状态
    public enum MouseState
    {
        LeftDown,
        LeftUp,
        RightDown,
        RightUp,
    }

    //自定义按钮含有鼠标状态的EventArgs
    public class BtnEventArgs : EventArgs
    {
        public MouseState BtnClickMouseState { get; private set; }
        public BtnEventArgs(MouseState mouseState)
        {
            BtnClickMouseState = mouseState;
        }
    }

2、发布者类:

  //发布者类
    class ButtonPublisher
    {
        private event EventHandler<BtnEventArgs> _tnEvent;

        public event  EventHandler<BtnEventArgs> BtnEvent
        {
            //若add 和 remove 访问器内不写任何代码,则添加移除事件注册无效。
            add
            {
                //加锁:避免在该事件实例正处理其他事情时,
                //同时执行该段代码,可能会产生某些问题
                lock (this)
                {
                    //在事件的注册列表里是否存在已注册的方法
                    bool isHavedEvent = false;

                    if (_tnEvent != null)
                    {
                        //遍历事件的注册列表
                        foreach (var en in _tnEvent.GetInvocationList())
                        {
                            var btnEventHandler = en as EventHandler<BtnEventArgs>;
                            
                            if (btnEventHandler == null || 
                            btnEventHandler.Method == null)
                                continue;

                            var method = btnEventHandler.Method;
                            //对比事件处理程序是否相同
                            if (method == value.Method)
                            {
                                isHavedEvent = true;
                                break;
                            }
                        }

                    }
                    //若还没有注册,就注册;否则,不执行重复注册
                    if (isHavedEvent == false)
                    {
                        _tnEvent += value;
                    }

                }

            }

            remove
            {
                lock (this)
                {
                    _tnEvent -= value;//移除事件注册
                }
                   
            }
        }

        public void RaiseBtnEvent(MouseState mouseState)
        {
            BtnEventArgs args = new BtnEventArgs(mouseState);

            if(_tnEvent != null)
            _tnEvent(this, args);
        }
    }

3、订阅者类

//订阅者类
    class Subscriber
    {
        public void MethodMouse(object o, BtnEventArgs e)
        {
            string str = Enum.GetName(typeof(MouseState), e.BtnClickMouseState);
            Console.WriteLine("{0}", str);
        }
    }

4、测试代码:

    class Program
    {
        static void Main(string[] args)
        {
            ButtonPublisher p = new ButtonPublisher();
            Subscriber s = new Subscriber();
            
            //注册了一次
            p.BtnEvent += s.MethodMouse;
            //由于add 访问器里有做了判断:相同的事件处理程序不能再被关联一遍
            p.BtnEvent += s.MethodMouse;

            Console.WriteLine("注册BtnEvent事件后,准备触发该事件:");
            //触发事件
            p.RaiseBtnEvent(MouseState.LeftUp);
            p.RaiseBtnEvent(MouseState.LeftDown);

            p.BtnEvent -= s.MethodMouse;
            Console.WriteLine("移除事件注册后,没法触发该事件");
            //因移除了该事件的注册,无法触发该事件
            p.RaiseBtnEvent(MouseState.LeftUp);
            p.RaiseBtnEvent(MouseState.LeftDown);
            Console.ReadKey();
        }
    }

输出结果:

注册事件后,触发事件:
LeftUp
LeftDown
移除事件注册后,不触发事件

加强理解委托和事件之间的关系

记住以下几点:
事件是类或结构提供具有通知能力的成员。

事件是一种特殊的多播委托。

事件包含了一个私有的委托。

事件成员被隐式自动初始化为 null;

委托是一个类,它封装了一个调用列表。使用到事件是因为要把委托包装起来,为了避免委托的滥用,使得保障使用委托的安全性。同时,事件还起到因隐藏对委托字段的访问限制作用,仅仅提供添加和移除事件处理程序的功能。于是事件作为发送者的成员,发送者调用它就不会对委托里所有功能都能操作,因为委托在事件里是私有的。

关于扩展 EventArgs,实际上是根据事件里私有委托已经设计好的泛型来实现的:

//在源代码中,有一个泛型委托,TEventArgs 是一个泛型参数
 public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e);

所以如果自定义一个委托,这时仅仅是一个委托,而不是事件,但也可以输出同样的结果。

//自定义一个委托
public delegate void CustomEventHandler(Object obj, BtnEventArgs e);

//把以上代码例子中的所有EventHandler<BtnEventArgs>替换为:
CustomEventHandler
//执行的功能和输出的结果一样。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Excel函数VLOOKUP常用方法

一、基础用法 1、精确匹配 公式&#xff1a;VLOOKUP(待匹配值&#xff0c;查找范围&#xff0c;范围列数&#xff0c;查找方式) 定义好要输出表的表头和第一列&#xff0c;第一列即为要查找和匹配的父内容&#xff0c;在第二列输入公式&#xff0c;被查找表中一定也要将待查…

基于SPAD / SiPM技术的激光雷达方案

激光雷达(LiDAR)是一种测距技术&#xff0c;近年来越来越多地用于汽车先进驾驶辅助系统(ADAS)、手势识别和3D映射等应用。尤其在汽车领域&#xff0c;随着传感器融合的趋势&#xff0c;LiDAR结合成像、超声波、毫米波雷达&#xff0c;互为补足&#xff0c;为汽车提供全方位感知…

【力扣刷题 | 第六天】

目录 前言&#xff1a; 344. 反转字符串 - 力扣&#xff08;LeetCode&#xff09; 541. 反转字符串 II - 力扣&#xff08;LeetCode&#xff09; 今天我们进入字符串章节的刷题旅程&#xff0c;希望各位小伙伴可以和我一起坚持下去&#xff0c;一起征服力扣&#xff01; 前言…

前端前端学习不断

卷吧卷吧...&#xff0c;这东西什么时候是个头啊……

半导体器件基础(期末模电速成)

目录 1、半导体分类 2、PN结 3、二极管 4、稳压二极管 5、三极管 6、场效应管 1、半导体分类 2、PN结 3、二极管 伏安特性&#xff1a; 我们第七版模电书上给的正向导通压降分别约为0.7和0.2V&#xff0c;且硅的单向导电性更好 如何确定二极管状态&#xff1f; 阳极电压…

怎么快速掌握Python爬虫技术?

Python总的来说是一门比较容易入门的编程语言&#xff0c;因为它的语法简洁易懂&#xff0c;而且有很多优秀的教程和资源可供学习。相比其他编程语言&#xff0c;Python 的学习曲线较为平缓&#xff0c;初学者可以很快上手&#xff0c;但要想深入掌握 Python&#xff0c;还需要…

6款AI绘画生成器,让你的创作更有灵感

人工智能绘画听起来很高深&#xff0c;其原理是通过集成文本、图片和其他大数据数据来生成信息库&#xff0c;在输入文本描述的要求后&#xff0c;可以找到相应的视觉元素&#xff0c;然后拼凑起来生成符合文本描述的图片。 本文介绍非常好用的6款AI绘画生成工具 1.即时 AI 绘…

location.href 和 document.URL 与 document.documentURI

location.href 和 document.URL 与 document.documentURI 相同点 获取到的值相同 不同点 location.hrefurl可以赋值, 效果类似location.assign(url) , 可以后退 document.URL 与 document.documentURI 是只读的, 赋值无效 location.href locationwindow.location true lo…

HTTP编码杂谈

一 HTTP编码杂谈 ① 知识铺垫 1) 编码的英文叫encode --> 常见HTTP URL编码、Base64编码等目的&#xff1a; 转变为二进制的stream(字节流),便于网络传输备注&#xff1a; 一般都是基于utf-8编码2) 解码叫decode3) 乱码的根源&#xff1a; 编码和解码的方式不一致4) url…

Flask开发简易网站疑难点梳理

文章目录 整体总结创建项目独立的python环境windows下python独立环境目录结构linux下python独立环境目录结构 大概需要安装的第三方库使用websockt实现python代码与html界面的通讯界面F12中看到提示连接成功后立马连接关闭。 linux下数据库查询异常初次登录web的时候背景图片和…

智能指针(2)

智能指针&#xff08;2&#xff09; shared_ptr(共享型智能指针)基础知识特点引用计数器共享型智能指针结构理解 shared_ptr仿写删除器类计数器类shared_ptr类使用以及仿写代码的理解 循环引用_Weaks 初始化智能指针的方法 shared_ptr(共享型智能指针) 基础知识 在java中有一…

Hive | 报错锦集

知识目录 一、写在前面✨二、Hive启动hiveserver2报错&#x1f525;三、HiveServer2启动方式✨四、Hive执行SQL语句报一大堆日志&#x1f349;五、Hive使用Load加载数据报错&#x1f36d;六、Hive执行含Count的SQL语句报错&#x1f349;七、Hive执行SQL语句报/bin/java&#x1…

openGauss5.0之学习环境 Docker安装

文章目录 0.前言1. 准备软硬件安装环境1.1 软硬件环境要求1.2 修改操作系统配置1.2.1 关闭操作系统防火墙 1.3 设置字符集参数1.4 设置时区和时间&#xff08;可选&#xff09;关闭swap交换内存1.5 关闭RemoveIPC1.6 关闭HISTORY记录 2. 容器安装2. 1支持的架构和操作系统版本2…

ChatGPT+小红书的8种高级玩法

掌握了这套万能命令&#xff0c;让你快速做出小红书爆款文案! 一、用ChatGPT做定位 我是一个大龄的普通人&#xff0c;没有什么特殊的技能&#xff0c;接下来&#xff0c;请你作为一位小红书的账号定位专家&#xff0c;通过与我对话的方式&#xff0c;为我找到我的小红书账号定…

记录一个Invalid bound statement (not found)问题

SpringBootMyBatisPlus项目&#xff0c;非常简单&#xff0c;没有任何业务逻辑&#xff1a; 1. pom文件 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.…

Java8 Stream详解及结束操作方法使用示例(三)

结束操作是指结束 Stream 该如何处理的操作&#xff0c;并且会触发 Stream 的执行。下面是一些常用的结束操作方法。结束操作会对数据源进行遍历&#xff0c;因此是及早求值的。 Java8 Stream详解及中间操作方法使用示例&#xff08;一&#xff09; ​​​​​​​Java8 Strea…

java生成、识别条形码和二维码

一、概述 使用 zxing 开源库 Zxing主要是Google出品的&#xff0c;用于识别一维码和二维码的第三方库主要类:BitMatrix 位图矩阵MultiFormatWriter 位图编写器MatrixToImageWriter 写入图片 可以生成、识别条形码和二维码 内置三种尺寸&#xff1a;enum Size {SMALL, MIDDLE, …

华为OD机试真题 JavaScript 实现【求符合要求的结对方式】【2023Q1 100分】,附详细解题思路

一、题目描述 用一个数组A代表程序员的工作能力&#xff0c;公司想通过结对编程的方式提高员工的能力&#xff0c;假设结对后的能力为两个员工的能力之和&#xff0c;求一共有多少种结对方式使结对后能力为N。 二、输入描述 6 2 3 3 4 5 1 6 第一行为员工的总人数&#xff…

计算机视觉-目标检测(一):从 R-CNN 到 Faster R-CNN

文章目录 1. 概要2. 区域卷积卷积神经网络R-CNN2.1 模型结构2.2 Selective Search2.3 warp2.4 R-CNN训练2.5 R-CNN推理2.6 R-CNN性能评价2.7 R-CNN的缺点 3. SPP-Net3.1 SPP-Net对RCNN的改进3.2 SPP-Net网络结构3.3 SPP-Net训练过程3.4 SPP-Net的问题 4. Fast R-CNN4.1 Fast R-…

河北沃克HEGERLS仓储货架生产厂家|夹抱式伸缩货叉四向穿梭车新型物流机器人

众所周知仓库作业主要是围绕存取、搬运、拣选、输送分拣而进行的&#xff0c;而随着物流作业的多样化、复杂化&#xff0c;四向穿梭车作为新的存储技术&#xff0c;以其灵活、柔性等特点而备受瞩目。河北沃克在成功研发四向穿梭车的基础上又对其进行了产品的横向发展。目前&…
最新文章