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

news2025/1/25 4:35:02

一、发布者和订阅者

发布者/订阅者模式(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.coloradmin.cn/o/632043.html

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

相关文章

SpringSecurity 总结

SpringSecurity 总结 第一章 权限管理 权限管理SpringSecurity 简介整体架构 权限管理&#xff1a; 实现: "对用户访问系统的控制"(身份认证) &#xff0c; 按照 "安全规则"或者 "安全策略" (对已经认证的用户进行授权) 控制&#xff0c;用…

C++教程(05)——数据类型

C 数据类型 使用编程语言进行编程时&#xff0c;需要用到各种变量来存储各种信息。变量保留的是它所存储的值的内存位置。这意味着&#xff0c;当您创建一个变量时&#xff0c;就会在内存中保留一些空间。 您可能需要存储各种数据类型&#xff08;比如字符型、宽字符型、整型…

2.DIY可视化-拖拽设计1天搞定主流小程序-PHP安装

DIY可视化-拖拽设计1天搞定主流小程序-PHP安装 前言 话不多说,直接实操。一、拉取代码并导入 https://gitee.com/diygw/diygw-ui-php 找到本机一个文件夹&#xff1a; git clone 导入vscode 二、打开小皮&#xff0c;新建网站 启动web服务&#xff1a; 创建网站&#xff…

视觉SLAM十四讲——ch5实践(相机与图像)

视觉SLAM十四讲----ch3的实践操作及避坑 一、实践操作前的准备工作二、各个实践操作1.计算机中的图像2.3D视觉 三、遇到的问题 一、实践操作前的准备工作 安装OpenCV Ubuntu18参考&#xff1a;Ubuntu 18.04下opencv4安装及C配置 Ubuntu20参考&#xff1a;Ubuntu 20.04搭建OpenC…

shell脚本学习记录1(运算符)

Shell 传递参数 我们可以在执行 Shell 脚本时&#xff0c;向脚本传递参数&#xff0c;脚本内获取参数的格式为&#xff1a;$n。n 代表一个数字&#xff0c;1 为执行脚本的第一个参数&#xff0c;2 为执行脚本的第二个参数&#xff0c;以此类推…… 以下实例我们向脚本传递三个…

普中自动下载软件1.86下载程序失败案例

今天在用开发板做一个功能&#xff0c;下载的时候报错了&#xff0c;说芯片超时 确定驱动安装好了的 波特率也试了一圈 线也换过了 最后发现是芯片类型选错了&#xff0c;这个开发板是用的stc89c52,所以我选了图里这个&#xff0c;但是翻了开发板配套的资料&#xff0c;发现…

数据库系统概论 --- 期末单元集

第一章 绪论 一、选择题&#xff08;必考题型&#xff09; 1&#xff0e;在数据管理技术的发展过程中&#xff0c;经历了人工管理阶段、文件系统阶段和数据库系统阶段。在这几个阶段中&#xff0c;数据独立性最高的是 阶段。 A&#xff0e;数据库系统 B&#xff0e;文件…

ICRA2024

ICRA 2024 2024 IEEE International Conference on Robotics and Automation 2024年IEEE国际机器人与自动化大会 官网 http://ieee-icra.org/index.html CONNECT The 2024 IEEE International Conference on Robotics and Automation (ICRA2024) is the IEEE Robotics and Auto…

分布式系统和高可用架构设计方案

目录 分布式系统 RPC 的工作原理 分布式数据存储 分布式锁 降级、熔断、限流 链路追踪 系统优化和故障处理 分布式系统 传统单体服务架构代码数量庞大&#xff0c;牵一发而动全身&#xff0c;一个很小的改动都可能影响整个服务。正所谓不要把所有的鸡蛋装在一个篮子里&…

大学物理(上)-期末知识点结合习题复习(3)——质点运动学-惯性系 非惯性系 惯性力 动量定理 动量守恒定律

目录 1.惯性系 2.非惯性系 3.惯性力 题1 题目描述 题解 4.动量定理 题2 题目描述 题解 5.动量守恒定律 题3 题目描述 题解 1.惯性系 牛顿定律适用的参考系&#xff0c;总能在找到特殊的参照物群&#xff08;参考系&#xff09;&#xff0c;使得牛顿第一定律成立…

02_LinuxLED驱动开发

目录 Linux下LED灯驱动原理 地址映射 ioremap函数 iounmap函数 I/O内存访问函数 LED灯驱动程序编写 编写测试APP 编译驱动程序 编译测试APP 运行测试 Linux下LED灯驱动原理 Linux下的任何外设驱动,最终都是要配置相应的硬件寄存器。所以LED灯驱动最终也是对I.MX6ULL…

前端面试题---事件循环机制和异步编程

一.JavaScript 中的事件循环&#xff08;Event Loop&#xff09;机制。 JavaScript 中的事件循环&#xff08;Event Loop&#xff09;是一种用于处理异步操作和事件的机制&#xff0c;它确保代码按照正确的顺序执行&#xff0c;并防止阻塞和死锁。 事件循环的工作原理如下&am…

2.Java概述|Java学习笔记

文章目录 Java的诞生Java重要特点JDK、JRE、JVMJava开发注意事项和细节说明注释Java 中的注释类型 Java的诞生 https://www.oracle.com/java/technologies/java-se-support-roadmap.html Java 技术体系平台&#xff1a; Java重要特点 Java 语言是面向对象的(oop)Java 语言…

Dcloud开发者注册,uniCloud服务空间创建。

一、注册dcloud开发者 1、登录dcloud官网&#xff0c;注册开发者账号&#xff0c;官网地址&#xff1a;https://www.dcloud.io/ 二、创建uniCloud服务空间 1、注册完成开发者后&#xff0c;点击开发者后台&#xff0c;点击uniCloud&#xff0c;进入uniCloud服务空间列表&…

shell脚本入门-多命令处理与变量

目录 1.多命令处理2.Shell变量Shell变量的介绍2.1 系统环境变量2.2自定义变量自定义局部变量查询变量值语法变量删除 自定义全局变量父子Shell环境介绍 2.3 自定义常量 1.多命令处理 多命令处理的介绍&#xff1a; 就是在Shell脚本文件中编写多个Shell命令 我们现在通过一些需…

自然语言处理实战10-文本处理过程与输入bert模型后的变化

大家好&#xff0c;我是微学AI&#xff0c;今天给大家介绍一下自然语言处理实战10-文本处理过程与输入bert模型后的变化&#xff0c;通过一段文本看看他的整个变化过程&#xff0c;经过怎样得变化才能输入到模型&#xff0c;输入到模型后文本又经过怎样的计算得到最后的结果。看…

逆向工程的未来在哪里?掌握逆向技能,开创新的职业道路!

前言 随着移动互联网的兴起&#xff0c;“APP”成了99%的互联网企业主要运营的产品&#xff0c;知名的例如“支付宝”、“美团”、“滴滴”、“抖音”等。用户基数的不断变大&#xff0c;安全性也经历着巨大的挑战。 app越来越多&#xff0c;也离不开我们的生活&#xff0c;而…

团队管理之性能实施团队日志10

在这一周中基本上遇到了性能实施过程中应该遇得到的复杂的问题。 像堆外内存引发OOM Killer&#xff0c;C coredump&#xff0c;负载该均衡不均衡&#xff0c;主机资源不够用&#xff0c;数据引发TPS抖动&#xff0c;IO引发TPS抖动之类的。 在这个项目中几乎碰到了我之前遇到…

2023Fiddler抓包学习笔记 -- 环境配置及工具栏介绍

一、Fiddler介绍 Fiddler是位于客户端和服务器端的HTTP代理&#xff0c;常用来抓http数据包&#xff0c;可以监控浏览器所有的http和https流量&#xff0c;查看分析请求数据包和响应数据包&#xff0c;伪造请求和响应等功能。 二、下载安装 1、下载地址 https://www.teleri…

企业——缺省路由

缺省路由是目的地址和掩码全为0的特殊路由 如果报文的目的地址无法匹配路由表中的任何一项&#xff0c;路由器将选择依照缺省路由来转发报文。 ip route-static 0.0.0.0 0.0.0.0 实验要求&#xff1a; 1、按照图中的要求配置IP 2、要求使用静态协议缺省实现访问2.0、3.0、…