C#事件实例详解

news2024/11/18 15:23:07

一、什么是事件?


    在C#中,事件(event)是一种特殊的类成员,它允许类或对象通知其他类或对象发生了某些事情。
    从语法上看,事件的声明类似于字段,但它们在功能和行为上有一些重要的区别。

    从技术角度来说,事件实际上是一个封装了事件订阅和取消订阅功能的委托字段。当声明一个事件时,编译器会在背后生成一个私有的委托字段,以及公共的添加(add)和移除(remove)访问器方法。

    例如事件声明时:

public event EventHandler<TextArgs> TextPublish;

编译器实际上会生成类似如下的代码:

	private EventHandler<TextArgs> _textPublish;
	public event EventHandler<TextArgs> TextPublish
	{
		add { _textPublish += value; }
		remove { _textPublish -= value; }
	}

    这里,_textPublish是一个私有的委托字段,它存储了所有订阅了TextPublish事件的事件处理方法。add和remove访问器方法提供了订阅和取消订阅事件的功能,它们分别对应了+=和-=操作符。

    所以,从这个角度来看,事件更像是一个特殊的属性(property),它封装了一个私有的委托字段,并提供了特定的访问方法。

    但是,与普通的属性不同,事件有一些特殊的限制:

    1. 事件只能在声明它的类内部直接调用。在类外部,只能通过+=和-=操作符来订阅和取消订阅事件,不能直接读取或赋值事件。

    2. 事件通常应该是公共的(public),以允许其他类订阅和取消订阅。但是,事件的访问器方法(add和remove)通常应该是私有的或受保护的,以防止在类外部直接调用。

    3. 事件通常应该在引发事件的类中声明,而事件处理方法通常应该在订阅事件的类中定义。这体现了事件的发布-订阅模型。

    所以,事件是一种特殊的类成员,它结合了字段、属性和方法的某些特性,用于实现事件驱动编程。理解事件的特殊性质,对于正确地使用和管理事件非常重要。


    
二、实例说明


    举例:在Form1与Form2中有button1和textbox1。
    点击Form1中的button1显示Form2。点击Form2中的button1,将Form2中textbox1中的内容发送到Form1中的textbox1中去。


    
    设计,用事件来处理。在Form2声明事件变量,实行触发与发布。在Form2中进行事件处理与事件订阅。

    form2中:

    public partial class Form2 : Form
    {
        public event EventHandler<TextArgs> TextPublish;//a

        public Form2()//b
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)//c
        {
            TextPublish?.Invoke(this, new TextArgs(textBox1.Text));//d
        }
    }

    public class TextArgs : EventArgs//e
    {
        public string Text;//f

        public TextArgs(string msg)//h
        {
            Text = msg;
        }
    }

    form1中:

    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            Form2 fr2 = new Form2();//i
            fr2.TextPublish += ShowFromForm2;//j
			fr2.Show();//k
        }

        public void ShowFromForm2(object o, TextArgs e)//m
        {
            textBox1.Text = e.Text;//n
        }
    }

    (1)问:a处是定义事件变量,如何理解?前后没看到它的实例化,它在哪儿?
    答:event关键字表示定义一个事件,EventHandler<TextArgs>是事件的委托类型,其中TextArgs是自定义的事件参数类。EventHandler<T>中第一个参数固定为object,表示事件的发送者,第二个参数为T类型,是事件的参数。TextPublish是这个事件的名字。事件的定义必须在类内部,因为它是类的成员。
    
    public event EventHandler<TextArgs> TextPublish;这行代码只是声明了一个事件,但并没有实例化。它类似于string a;,只是声明了一个字段,但没有给它赋值。

    在C#中,事件的声明类似于字段,但又有一些不同。事件声明后,编译器会自动生成一个私有的委托字段来存储事件的处理程序,并生成add和remove访问器来添加或移除事件处理程序。所以我们可以直接使用+=和-=操作符来订阅和取消订阅事件。

    在d处,TextPublish.Invoke(...)能够成功调用,是因为在调用之前,这个事件已经被其他地方的代码订阅了(j处),也就是说有地方使用了TextPublish += ...;的语句。如果事件没有被任何地方订阅,那么Invoke调用时,TextPublish的值实际上是null,会抛出NullReferenceException异常。
    
    不必显式地实例化事件。
    当其他地方(如Form1)订阅了TextPublish事件时,C#编译器会自动实例化TextPublish事件的委托字段。这个委托字段最初为null,当第一次使用+=操作符添加事件处理程序时,编译器会创建一个新的委托实例,并将其赋值给这个字段。此后,这个字段就不再为null了。每次使用+=添加新的事件处理程序,都会创建一个新的委托实例,并将其与现有的委托实例合并。

    这个自动实例化的过程是由C#编译器在背后完成的,不需要手动去实例化事件。这也是为什么可以直接使用+=和-=操作符来管理事件订阅的原因。这种设计大大简化了事件的使用,让我们可以专注于事件的订阅和发布,而不需要关心事件的实例化细节。
    
    
    
    
    
    (2)问:b处警告退出时textpublish必须非空?是什么意思,会有什么后果,如何避免?
    答:警告说明:在Form2的构造函数退出时,TextPublish事件必须被赋值,不能为null。如果TextPublish为null,在调用事件时会导致NullReferenceException。为避免这个问题,你可以在构造函数中为TextPublish赋一个空的事件处理程序,或者在调用前判断是否为null。
    
    
    (3)问:c处单击方法里面一般不直接发布?
    答:通常在UI事件的处理方法中,如button1_Click,我们一般不直接调用事件的发布方法。而是先进行一些必要的检查和数据准备,然后再发布事件。这是一种良好的编程实践。
    
    (4)问:d处的this是引用的form2?而不是c中参数object的引发者?
    答:d处的this确实是指代的当前Form2的实例,而不是Click事件的sender参数。因为我们是在Form2内部发布自己的TextPublish事件。
    
    另外d处并不是最佳写法。应改为:

	private void button1_Click(object sender, EventArgs e)
	{
		string text = textBox1.Text;
		if (!string.IsNullOrWhiteSpace(text))
		{
			OnTextPublish(new TextArgs(text));
		}
	}

	protected virtual void OnTextPublish(TextArgs e)
	{
		TextPublish?.Invoke(this, e);
	}

    将触发和发布分开写可以提高代码的灵活性和可维护性,使代码更易于理解、扩展和修改。例如:
        在发布事件前,可以进行必要的数据检查和准备工作,如上面的代码检查了文本是否为空。
        发布事件的逻辑被封装在一个单独的受保护的虚方法OnTextPublish中,子类可以重写这个方法来添加或修改发布事件的逻辑。
        在OnTextPublish中,使用了null条件运算符?.来检查事件是否为null,避免了可能的NullReferenceException。事件发布的方法还可以提供一些额外的处理逻辑,例如日志记录、异常处理等。在OnTextPublish方法中,可以针对特定的事件进行一些前置或后置处理,以增加代码的健壮性和可靠性。
        
    这种模式在.NET的类库中广泛使用,它提供了更好的灵活性、可扩展性和安全性。
    
    
    
    (5)问:e一般继承事件参数,是按照单击事件后面跟e参数一样?f处就跟具体的信息,由h处构造函数传送过来?
    答:对,自定义事件的参数类一般继承自EventArgs,并添加需要传递的数据作为公共属性,就像TextArgs.Text一样。这些数据在构造函数中初始化,在事件处理程序中使用。
    
    EventHandler<TextArgs>说明EventHandler<T>预定义的第二个参数必须是EventArgs的派生类,如果自定义的事件参数类不继承EventArgs(e处),就会产生类型不兼容的错误。所以按照约定,自定义事件参数类都应该直接或间接继承自EventArgs。
    
    也可以不使用继承EventArgs的类型,比如EventHandler<string>。然后直接传递一个字符串作为事件参数。这在语法上是允许的。但是,这并不是一个好的实践。
    
    使用EventArgs派生类有三处好处,表示这个类是事件参数类、传递多个参数时直接融入多个属性即可、方便自己和他人阅读和维护。所以,即使事件参数只有一个字符串,也建议定义一个继承自EventArgs的类来包装它。
    
    
    
    (6)问:h处警告:使用主构造函数,这是什么意思?只听说过构造函数,没听说过主构造函数?
    答:"使用主构造函数"警告可能是因为你使用了C# 9.0或更高版本引入的"主构造函数"特性。主构造函数可以简化类的定义,将构造函数参数直接放在类名后面,省去了单独定义字段的麻烦。不过主构造函数只能有一个,如果定义了主构造函数,就不能再有其他构造函数了。这个警告提示你考虑使用主构造函数语法。
    
    主构造函数允许你在类名后面直接添加构造函数参数,编译器会自动生成对应的私有字段和构造函数。例如:

	public class Person
	{
		public Person(string firstName, string lastName)
		{
			FirstName = firstName;
			LastName = lastName;
		}

		public string FirstName { get; }
		public string LastName { get; }
	}

    可以简写为:

	public class Person(string FirstName, string LastName)
	{
	}

    主构造函数可以简化类的定义,减少重复代码。
    但它也有一些限制,例如只能有一个主构造函数,如果定义了主构造函数就不能再定义其他构造函数等。

    传统的构造函数语法更灵活,可以定义多个重载的构造函数,也可以在构造函数中编写更复杂的初始化逻辑。
    主构造函数更适合那些简单的、只需要初始化几个属性的类。
    
    对于有默认值的主构造函数还可以这样:

	public class Person(string FirstName = "John", string LastName = "Doe")
	{
	}

在主构造函数中为参数提供默认值,相当于为属性提供了默认值。如果在创建类的实例时没有提供参数,那么这些属性就会使用默认值。

	public class Person(string FirstName = "John", string LastName = "Doe")
	{
	}

	// 使用默认值
	Person p1 = new Person();
	Console.WriteLine($"{p1.FirstName} {p1.LastName}"); // 输出: John Doe

	// 提供参数
	Person p2 = new Person("Jane", "Smith");
	Console.WriteLine($"{p2.FirstName} {p2.LastName}"); // 输出: Jane Smith

    
    (7)问:m处事件的处理,该方法需要与a处签名一致?
    答:事件处理方法的签名必须与事件的委托类型匹配。在这个例子中,TextPublish事件的类型是EventHandler<TextArgs>,所以事件处理方法ShowFromForm2必须有一个object类型的参数(事件发送者)和一个TextArgs类型的参数(事件参数)。在方法体内,我们可以直接使用事件参数e的属性,如e.Text。
    
    
    (8)问:i处实例化时就自动有了TextPubublish成员,通过+=实现了form2中事件变量的变例化,原为null的textpublish得到了showformform2因此不空了。
    答:当Form2的实例fr2被创建时,它的TextPublish事件字段初始为null。但在下一行代码fr2.TextPublish += ShowFromForm2;中,通过+=操作符,将ShowFromForm2方法订阅到了TextPublish事件。这个操作会导致TextPublish字段被实例化为一个新的委托对象,并将ShowFromForm2方法添加到这个委托对象中。因此,TextPublish不再为null。
    
    
    (9)问:k处用了show。如果用了showdialog()将是模态输出,可能还有一个隐患?
    答:Show方法会以非模态的方式显示Form2,这意味着Form1还可以继续响应用户的交互。而ShowDialog会以模态的方式显示Form2,这意味着在Form2关闭之前,用户不能与Form1交互,因此ShowDialog后面的语句可以无法得到Form1的响应。例如:
    Form2 fr2 = new Form2();//i
    fr2.ShowDialog();//k
    fr2.TextPublish += ShowFromForm2;//j    
    当k语句执行后,因为模态的原因,form1将“冻结”无法响应,也就无法继续执行j句,因此,后面点击后事件实际上没有发生,因为j句没有执行,那么事件就没有实例化是一个null,在d处是一个空合并操作,为null不会执行后面操作,所以没有反应。如果改为TextPublish.Invoke(this, new TextArgs(textBox1.Text));在点击后因为TextPublish为空将出现异常。所以这里最好用show,若用showdialog则放在最后。
    

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

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

相关文章

通过JWT完成token登录验证

前言 什么是JWT&#xff1f; 全称是JSON Web token&#xff0c;是用于对应用程序上的用户进行身份验证的标记&#xff0c;使用 JWTS 的应用程序不再需要保存有关其用户的 cookie 或其他session数据 使用JWT的优势 提高了程序的可伸缩性&#xff0c;也极大的提高了应用程序的安全…

鸿蒙Harmony应用开发—ArkTS(@Link装饰器:父子双向同步)

子组件中被Link装饰的变量与其父组件中对应的数据源建立双向数据绑定。 说明&#xff1a; 从API version 9开始&#xff0c;该装饰器支持在ArkTS卡片中使用。 概述 Link装饰的变量与其父组件中的数据源共享相同的值。 限制条件 Link装饰器不能在Entry装饰的自定义组件中使用…

前端canvas项目实战——简历制作网站(六):加粗、斜体、下划线、删除线(上)

目录 前言一、效果展示二、实现步骤1. 视图部分&#xff1a;实现用于切换字体属性的按钮2. 逻辑部分&#xff1a;点击按钮之后要做什么&#xff1f;3. 根据Textbox的属性实时更新按钮的状态 三、Show u the code后记 前言 上一篇博文中&#xff0c;我们实现了对文字的字体、字…

ChatGLM3 Linux 部署

1.首先需要下载本仓库&#xff1a; git clone https://github.com/THUDM/ChatGLM3 2.查看显卡对应的torch 版本 官方文档说明&#xff1a; Start Locally | PyTorch 例如&#xff1a; a. 先查看显卡的CUDA版本 nvcc --version 查看对应版本 Previous PyTorch Versions …

Error:No such property: GradleVersion for class: JetGradlePlugin

Gradle版本对照表 Android Gradle 插件版本在项目的根目录&#xff08;不是App目录&#xff09;下的build.gradle文件中&#xff0c;如图 插件所需的Gradle 版本在gradle目录下的gradle-wrapper.properties文件中&#xff0c;如图

安全认证|CISSP认证是什么证书?考了有什么用?能做什么工作?

很多人总是听说CISSP是顶级的信息安全证书&#xff0c;在国内或者国外都有盛誉&#xff0c;那么CISSP到底是个什么样的证书&#xff0c;本期就给大家介绍下&#xff01; 什么是CISSP CISSP&#xff08;Certification for Information System Security Professional&#xff0…

三份天注定,七分靠XX?

文 | 螳螂观察 作者 | 陈小江 1988年&#xff0c;中国宝岛台湾&#xff0c;蒋经国过世后&#xff0c;社会运动风起云涌。在所谓“解严”的时代氛围里&#xff0c;人们对前途虽然迷茫&#xff0c;但却充满打拼的热情。 那时节&#xff0c;40岁的台湾歌手叶启田&#xff0c;开…

【消息队列开发】 实现消费者订阅消息

文章目录 &#x1f343;前言&#x1f333;关于订阅消息方法参数解析&#x1f38b;如何实现将消息推送给消费者&#x1f38d;消费者类&#x1f340;消费消息的流程&#x1f384;如何实现消息确认呢&#xff1f;⭕总结 &#x1f343;前言 本次开发任务 实现消费者订阅消息 &am…

公司内部局域网怎么适用飞书?

随着数字化办公的普及&#xff0c;企业对于内部沟通和文件传输的需求日益增长。飞书作为一款集成了即时通讯、云文档、日程管理、视频会议等多种功能的智能协作平台&#xff0c;已经成为许多企业提高工作效率的首选工具。本文将详细介绍如何在公司内部局域网中应用飞书&#xf…

电脑Wi-Fi无法连接如何排查

Wi-Fi是一个神奇的东西&#xff0c;总是能在某一天莫名其妙的连不上让我们疯狂糟心&#xff01;&#xff01;&#xff01; 呉師傅准备了几个解决方法来帮助大家解决连不上Wi-Fi的问题&#xff1b; 1、疑难解答功能 系统自带的【疑难解答】功能不妨试一试&#xff0c;也能一定…

【AAAI 2024】M2Doc:文档版面分析的可插拔多模态融合方法

一、文章介绍 文档版面分析任务是文档智能的一个关键任务。然而&#xff0c;现有的很多文档版面分析研究方法都基于通用目标检测方法&#xff0c;忽视了文档的文本特征而仅仅只关注于视觉特征。近年来&#xff0c;基于预训练的文档智能模型在很多文档下游任务中都取得了成功&a…

左旋字符串功能的实现

实现一个函数&#xff0c;可以左旋字符串中的k个字符。 例如&#xff1a; #1ABCD左旋一个字符得到BCDA #2ABCD左旋两个字符得到CDAB 由此图可知&#xff0c;其字符串长度为4&#xff0c;每次经历四次左旋后又回到了初始 位置&#xff0c;所以是以字符串长度len为一个循环&…

微服务cloud--抱团取暖吗 netflix很多停更了

抱团只会卷&#xff0c;卷卷也挺好的 DDD 高内聚 低耦合 服务间不要有业务交叉 通过接口调用 分解技术实现的复杂性&#xff0c;围绕业务概念构建领域模型&#xff1b;边界划分 业务中台&#xff1a; 数据中台&#xff1a; 技术中台&#xff1a; 核心组件 eureka&#x…

(done) ROC曲线 和 AUC值 分别是什么?

来源&#xff1a;https://www.bilibili.com/video/BV1wz4y197LU/?spm_id_from333.337.search-card.all.click&vd_source7a1a0bc74158c6993c7355c5490fc600 在二分类问题下&#xff0c;我们的模型通常会输出一个 概率值&#xff0c;通过判断 概率值 和 阈值threshold 的大小…

docker 安装部署 jenkins

今天 小☀ 给大家普及一下什么是 jenkins&#xff01;&#xff01; Jenkins是一个开源软件项目&#xff0c;基于Java开发的持续集成工具。它提供了一个开放易用的软件平台&#xff0c;使软件项目可以进行持续集成。Jenkins起源于Hudson&#xff0c;主要用于持续、自动地构建、…

动态内存数组(malloc、calloc、realloc、free)

一、为什么要创建动态内存数组 动态内存&#xff0c;顾名思义就是说在内存中非固定的申请数组 在学习该项方法前我们申请内存的方法无非就两种&#xff1a;直接创建变量/通过创建数组的方式来申请空间。 那么直接创建变量/通过创建数组的方式来申请空间的缺点就是一旦创建成…

基于python+vue拍卖行系统的设计与实现flask-django-nodejs-php

拍卖行系统的目的是让使用者可以更方便的将人、设备和场景更立体的连接在一起。能让用户以更科幻的方式使用产品&#xff0c;体验高科技时代带给人们的方便&#xff0c;同时也能让用户体会到与以往常规产品不同的体验风格。 与安卓&#xff0c;iOS相比较起来&#xff0c;拍卖行…

2024学习鸿蒙开发,未来发展如何?

一、前言 想要了解一个领域的未来发展如何&#xff0c;可以从如下几点进行&#xff0c;避免盲从&#xff1a; 国家政策落地情况就业市场如何学习 通过上述三点&#xff0c;就能分析出一个行业的趋势。大家可以看到&#xff0c;我上面的总体逻辑就是根据国家政策来分析未来方…

大数据技术在工厂生产数字转型中的应用与价值

hello宝子们...我们是艾斯视觉擅长ui设计和前端开发10年经验&#xff01;希望我的分享能帮助到您&#xff01;如需帮助可以评论关注私信我们一起探讨&#xff01;致敬感谢感恩&#xff01; 随着大数据技术的快速发展&#xff0c;越来越多的企业开始关注并应用大数据技术&#x…