news 2026/1/17 22:19:39

.NET进阶——深入理解委托(3)事件入门

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
.NET进阶——深入理解委托(3)事件入门

为什么我要把事件放在委托这个专题里呢?主要的原因是事件是委托的高级封装

换句话说,先有委托才有事件,委托是事件的基础,事件是委托的封装

我们先看一个不用委托的例子,这个代码要求实现这样的功能:小猫叫->小孩哭->妈妈安慰->爸爸询问->邻居抱怨,这样的一个由小猫叫引发的一系列事件。

同时这个例子也是一个简单的观察者模式,如果没有了解过观察者模式,请大家仔细阅读代码或者询问AI大模型,相信你可以理解。

usingSystem;// 第一步:定义委托(小猫叫的方法签名:无参数、无返回值)publicdelegatevoidCatCryHandler();// 第二步:定义猫类(发布者),包含公共委托字段publicclassCat{// 公开的委托字段(无任何封装)publicCatCryHandlerCatCryDelegate;// 小猫叫的方法publicvoidMiao(){Console.WriteLine("🐱 小猫:喵呜~~~");// 调用委托,触发所有绑定的方法CatCryDelegate?.Invoke();}}// 第三步:定义订阅者(小孩、妈妈、爸爸、邻居)publicclassChild{publicvoidCry()=>Console.WriteLine("👶 小孩:哇呜呜呜,怕怕~");}publicclassMother{publicvoidComfort()=>Console.WriteLine("👩 妈妈:宝宝不怕,妈妈抱~");}publicclassFather{publicvoidAsk()=>Console.WriteLine("👨 爸爸:咋了?猫又叫了?");}publicclassNeighbor{publicvoidAngry()=>Console.WriteLine("👴 邻居:大半夜的,吵死了!");}// 测试代码classProgram{staticvoidMain(string[]args){// 1. 创建对象Catkitty=newCat();Childchild=newChild();Mothermom=newMother();Fatherdad=newFather();Neighborneighbor=newNeighbor();// 2. 绑定委托(订阅)kitty.CatCryDelegate+=child.Cry;kitty.CatCryDelegate+=mom.Comfort;kitty.CatCryDelegate+=dad.Ask;kitty.CatCryDelegate+=neighbor.Angry;// ❌ 问题1:外部可以直接赋值,覆盖所有之前的绑定!// 比如不小心写了=,而不是+=,之前的4个方法全没了kitty.CatCryDelegate=child.Cry;// 现在委托里只剩小孩哭,其他都没了// ❌ 问题2:外部可以直接调用委托,不用等小猫叫!Console.WriteLine("=== 外部直接调用委托(小猫还没叫)===");kitty.CatCryDelegate.Invoke();// 直接触发小孩哭,逻辑混乱// ❌ 问题3:外部可以直接置空委托,清空所有绑定kitty.CatCryDelegate=null;// 3. 调用小猫叫方法,但委托已经被置空,啥都不执行Console.WriteLine("\n=== 小猫真的叫了 ===");kitty.Miao();Console.ReadLine();}}
运行结果(暴露的坑):
=== 外部直接调用委托(小猫还没叫)=== 👶 小孩:哇呜呜呜,怕怕~ === 小猫真的叫了 === 🐱 小猫:喵呜~~~

能看到:直接用公共委托字段,外部可以随意修改、触发、清空委托,完全破坏了 “只有小猫叫才触发动作” 的逻辑 —— 这就是事件要解决的核心问题:给委托加 “保护罩”。

二、第二步:引入事件,解决委托的坑

事件的本质就是 “封装委托的保护罩”,只开放「订阅(+=)」和「取消订阅(-=)」,禁止外部赋值、直接调用、置空。

核心改法:把委托字段换成事件

只需要改猫类里的一行代码,再理解事件的核心规则即可:

publicclassCat{// ❌ 原来的公共委托字段(有坑)// public CatCryHandler CatCryDelegate;// 改成事件(基于同一个委托)publiceventCatCryHandlerCatCryEvent;publicvoidMiao(){Console.WriteLine("🐱 小猫:喵呜~~~");// 只有猫类内部能调用事件(触发委托)CatCryEvent?.Invoke();}}

三、完整的 “小猫叫 + 事件” 实现(从头写,逐行解释)

下面是完整、可运行的代码,每一步都配解释,跟着看就能懂:

usingSystem;// ===================== 第一步:定义委托(事件的“底层契约”)=====================// 委托定义了“小猫叫要触发的方法”的签名:无参数、无返回值// 所有要绑定到事件的方法,必须符合这个签名publicdelegatevoidCatCryHandler();// ===================== 第二步:定义发布者(猫类,拥有事件)=====================publicclassCat{// 定义事件:语法是「public event 委托类型 事件名;」// 编译器会自动生成:私有委托字段 + 仅开放+=/-=的add/remove方法publiceventCatCryHandlerCatCryEvent;// 小猫叫的核心方法(只有这个方法能触发事件)publicvoidMiao(){Console.WriteLine("\n🐱 小猫:喵呜~~~");// 触发事件(调用底层委托):只有猫类内部能执行这行代码!// ?. 是“空值保护”:如果没有订阅者,委托为null,不会报错CatCryEvent?.Invoke();}}// ===================== 第三步:定义订阅者(关注小猫叫的对象)=====================// 订阅者1:小孩publicclassChild{publicstringName{get;}publicChild(stringname)=>Name=name;// 订阅方法:签名必须和委托CatCryHandler一致(无参数、无返回值)publicvoidCry()=>Console.WriteLine($"👶{Name}:哇呜呜呜,怕小猫~");}// 订阅者2:妈妈publicclassMother{publicstringName{get;}publicMother(stringname)=>Name=name;publicvoidComfortChild()=>Console.WriteLine($"👩{Name}:宝宝不怕,小猫不咬人~");}// 订阅者3:爸爸publicclassFather{publicstringName{get;}publicFather(stringname)=>Name=name;publicvoidCheckCat()=>Console.WriteLine($"👨{Name}:别慌,我去看看小猫~");}// 订阅者4:邻居publicclassNeighbor{publicstringName{get;}publicNeighbor(stringname)=>Name=name;publicvoidComplain()=>Console.WriteLine($"👴{Name}:谁家的猫啊,吵死了!");}// ===================== 第四步:使用事件(订阅、触发、取消订阅)=====================classProgram{staticvoidMain(string[]args){// 1. 创建发布者(小猫)Catkitty=newCat();// 2. 创建订阅者ChildxiaoMing=newChild("小明");MotherliLi=newMother("李丽");FatherzhangSan=newFather("张三");NeighborwangYe=newNeighbor("王大爷");// 3. 订阅事件(外部只能用 +=,不能用=!)Console.WriteLine("=== 开始订阅小猫叫事件 ===");kitty.CatCryEvent+=xiaoMing.Cry;// 小明订阅:小猫叫→小明哭kitty.CatCryEvent+=liLi.ComfortChild;// 李丽订阅:小猫叫→妈妈安慰kitty.CatCryEvent+=zhangSan.CheckCat;// 张三订阅:小猫叫→爸爸查看kitty.CatCryEvent+=wangYe.Complain;// 王大爷订阅:小猫叫→邻居抱怨// 4. 触发事件(只能通过猫类的Miao方法,外部不能直接调用!)Console.WriteLine("\n=== 第一次小猫叫 ===");kitty.Miao();// 5. 取消订阅(外部只能用 -=)Console.WriteLine("\n=== 王大爷取消订阅 ===");kitty.CatCryEvent-=wangYe.Complain;// 王大爷不想听了,取消订阅// 6. 再次触发事件Console.WriteLine("\n=== 第二次小猫叫(王大爷已取消)===");kitty.Miao();// ❌ 以下操作全部编译报错(事件的保护机制),注释掉可验证:// kitty.CatCryEvent = xiaoMing.Cry; // 错误:不能用=赋值,只能+=/-=// kitty.CatCryEvent.Invoke(); // 错误:外部不能直接触发事件// kitty.CatCryEvent = null; // 错误:外部不能置空事件Console.ReadLine();}}
运行结果(符合预期,无安全隐患):
=== 开始订阅小猫叫事件 === === 第一次小猫叫 === 🐱 小猫:喵呜~~~ 👶 小明:哇呜呜呜,怕小猫~ 👩 李丽:宝宝不怕,小猫不咬人~ 👨 张三:别慌,我去看看小猫~ 👴 王大爷:谁家的猫啊,吵死了! === 王大爷取消订阅 === === 第二次小猫叫(王大爷已取消)=== 🐱 小猫:喵呜~~~ 👶 小明:哇呜呜呜,怕小猫~ 👩 李丽:宝宝不怕,小猫不咬人~ 👨 张三:别慌,我去看看小猫~

四、拆解事件的核心规则(结合小猫例子)

用表格总结,每一条都对应上面的代码,一看就懂:

操作 / 规则具体说明(小猫例子)是否允许
定义事件猫类里写public event CatCryHandler CatCryEvent;✅ 必须在类内部定义
订阅事件外部用kitty.CatCryEvent += 方法名(如+= xiaoMing.Cry✅ 外部仅允许这个操作
取消订阅外部用kitty.CatCryEvent -= 方法名(如-= wangYe.Complain✅ 外部仅允许这个操作
触发事件只有猫类内部能写CatCryEvent?.Invoke()(在 Miao 方法里)❌ 外部绝对不能
直接赋值事件外部写kitty.CatCryEvent = xiaoMing.Cry❌ 编译报错
置空事件外部写kitty.CatCryEvent = null❌ 编译报错
事件的本质编译器自动生成 “私有委托字段 + 仅开放 +=/-= 的方法”✅ 不用自己写,编译器帮你封装

五、进阶:用内置委托(Action)简化代码(实战常用)

上面我们自定义了CatCryHandler委托,实际开发中可以用 .NET 内置的Action(无参数、无返回值),省去自定义委托的步骤,代码更简洁:

usingSystem;// 猫类:直接用Action定义事件,无需自定义委托publicclassCat{// 用内置Action替代自定义CatCryHandlerpubliceventActionCatCryEvent;publicvoidMiao(){Console.WriteLine("\n🐱 小猫:喵呜~~~");CatCryEvent?.Invoke();}}// 订阅者、测试代码和之前完全一样,无需修改!// (因为Action的签名就是“无参数、无返回值”,和我们的订阅方法匹配)

运行结果和之前完全一致,但少写了public delegate void CatCryHandler();这一行 —— 这是实际开发中最常用的写法。

总结(核心要点,记牢这 3 条就够了)

  1. 事件的本质:是委托的 “安全封装”,就像给委托加了个 “保护罩”,只允许外部做「订阅(+=)」和「取消订阅(-=)」;

  2. 核心权限:只有定义事件的类(猫类)能触发事件(调用Invoke),外部只能订阅 / 取消订阅,不能赋值、不能直接触发、不能置空;

  3. 使用流程

    • 定义委托(或用内置 Action/Func)→ 类里定义事件 → 外部订阅事件 → 类内部触发事件 → (可选)外部取消订阅。

用小猫叫的例子再梳理一遍:猫(事件拥有者)只在 “叫” 的时候触发事件,小孩 / 妈妈 / 邻居(订阅者)只能选择 “听”(订阅)或 “不听”(取消订阅),不能强迫猫叫(外部触发),也不能把别人的 “听” 权限删掉(覆盖委托)—— 这就是事件的核心逻辑。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/1/18 0:02:42

PHP 表单 - 必需字段

PHP 表单 - 必需字段 在网站开发过程中,表单是用户与网站之间交互的重要途径。表单不仅用于收集用户信息,还可以实现用户注册、留言反馈等功能。然而,为了确保表单数据的准确性和完整性,设计表单时必须考虑添加必需字段。本文将详细介绍PHP中如何处理表单的必需字段,包括…

作者头像 李华
网站建设 2026/1/5 22:05:57

OEC-T改造手记(三):避坑指南!在Armbian上完美安装并配置Jellyfin

经历了基础搭建和文件同步,我的OEC-T终于迎来了最终使命——成为家庭媒体中心。我选择了 Jellyfin 这款开源免费的媒体服务器软件。然而,从安装、配置到刮削媒体信息,整个过程可谓“坑”不断,特此整理成避坑指南。 一、安装方式的…

作者头像 李华
网站建设 2026/1/18 2:53:02

Python 3 解释器

Python 3 解释器 引言 Python 是一种广泛应用于各种编程领域的解释型、高级编程语言。Python 3 是 Python 语言的一个主要版本,自 2008 年发布以来,它已经成为了最受欢迎的编程语言之一。本文将详细介绍 Python 3 解释器,包括其特点、安装方法以及一些常见问题。 Python …

作者头像 李华
网站建设 2026/1/11 17:11:57

Chart.js 雷达图深度解析

Chart.js 雷达图深度解析 引言 雷达图,作为一种特殊的图表,能够将多维度的数据在一个平面上直观地展现出来。在Web开发领域,Chart.js 是一个功能强大的图表库,它提供了多种图表类型,其中包括雷达图。本文将深入解析 Chart.js 雷达图的使用方法、特性以及优化技巧。 雷达…

作者头像 李华
网站建设 2025/12/25 12:22:11

网络安全实战能力怎么练?从新手到资深的3阶段提升指南

一、先搞懂:实战能力≠理论堆砌,核心是问题解决思维 很多新手误以为学完工具用法就是会实战,实则不然。举个典型例子:同样是学Nessus漏洞扫描,只懂理论的新手会直接跑全量扫描,导出几十页报告就交差。有实战…

作者头像 李华