在上一文我们已经大致介绍了关于事件的基础入门,现在我们来一个事件的实战,让大家更加深入的理解事件。这个代码也是观察者模式,如果对这个模式不清楚,可以看我上一个文章:.NET进阶——深入理解委托(3)事件入门
一、前景提要
1.1EventHandler介绍
我们这个代码中的委托使用系统自带的EventHandler委托:
publicdelegatevoidEventHandler(object?sender,EventArgse);这个委托没有返回值,但需要传递两个参数,一个是object类型的,一个是EventArgs类型的,这两个参数分别是什么意思呢?其实,object类型就是调用者,剩下的EventArgs就是要传递的参数,我这么说可能有点不好理解,接着看下去你就懂了。
1.2 场景背景介绍
你住在 “幸福小区”,物业负责通知业主停水 / 停电等事宜:
- 第一步:业主订阅通知张三、李四、王五去物业前台登记:“停水通知一定要告诉我!”(对应代码里的
+= 订阅事件); - 第二步:物业准备通知内容物业确定:“明天(2025-12-15)上午 8 点到 12 点,小区停水 4 小时,原因是水管维修”(对应代码里的
WaterStopNotice参数); - 第三步:物业挨家挨户通知物业人员上门,对每个登记的业主说:“我是幸福小区物业(
sender),通知你:明天 8 点 - 12 点停水 4 小时(e参数)”(对应代码里的Invoke传参); - 第四步:业主响应通知张三:“知道了,我囤点水”;李四:“我提前洗好衣服”;王五:“我提醒爸妈别忘接水”(对应代码里的
Buy方法执行不同逻辑)。
二、代码详情
2.1 传递信息类:WaterStopNotice
publicclassWaterStopNotice:EventArgs{publicDateTimeStopTime{get;set;}// 停水开始时间publicintDuration{get;set;}// 停水时长(小时)publicstringReason{get;set;}// 停水原因}首先,这个类是我们需要传递的信息类,可以看到它是继承自EventArgs类,所以这个类是可以当成EventArgs类传递信息的。
2.2 小区物业类:CommunityProperty
publicclassCommunityProperty{publicstringPropertyName{get;set;}// 物业名称(比如“幸福小区物业”)publicstringPhone{get;set;}// 物业联系电话// 定义“停水通知”事件(对外暴露,业主只能订阅/取消)publiceventEventHandlerWaterStopEvent;// 物业内部的“发通知”方法(对应代码里的PublicShow)publicvoidSendWaterStopNotice(){Console.WriteLine($"【{PropertyName}】开始通知业主停水事宜!");// 准备通知详情(e参数)WaterStopNoticenotice=newWaterStopNotice(){StopTime=newDateTime(2025,12,15,8,0,0),Duration=4,Reason="小区主水管维修"};// 触发事件:挨家挨户通知(传2个参数:物业自己+通知详情)WaterStopEvent.Invoke(this,notice);}}可以看到在这个类中,我们定义了两个属性,还定义了一个事件:
publiceventEventHandlerWaterStopEvent;这个事件是把EventHanlder这个委托封装了,我们在1.1讨论了它的两个参数,一个是object类型,一个是EventArgs类型,现在我们把这个委托封装从事件后,这个事件WaterStopEvent也就跟该委托一样,只能接收带有两个参数object? sender和EventArgs e,并且没有返回值的方法。
其次,我们还定义了触发方法SendWaterStopNotice,这个方法里我们首先创建了WaterStopNotice的实例对象,用这个实例对象来传递参数,并且我们还触发了事件WaterStopEvent.Invoke(this, notice);,即调用SendWaterStopNotice方法,就可以触发绑定的事件。
触发这个事件时,我们传递了两个参数,一个是this,另一个是刚刚创建的实例notice,notice比较好理解,就是我们创建的用来通知小区业主的信息,但是前面的this是啥呢?
我们都知道this指向的是实例本身,也就是说这里的this传递的是我们后续要将校区业主类CommunityProperty实例化后的那个对象,那么为什么要传递这个实例对象呢?想一想,如果你是小区业主,当有人通知你要停水时,你是不是要确认一下对方是谁,只有确认对方是物业后,你才会选择相信。那么这里也是一样的道理,由于我们的对象中有两个属性,一个是PropertyName物业名字,另一个是物业的电话Phone,所以我们要把这些信息也传递给小区业主,让他们知道发起这个消息的是谁,并且发起消息的人的电话。
2.2 物业类:HouseOwner
publicclassHouseOwner{publicstringName{get;set;}// 业主姓名publicintHouseNumber{get;set;}// 门牌号// 接收通知的方法(对应代码里的Buy)// sender = 物业实例,e = 停水通知详情publicvoidReceiveNotice(object?sender,EventArgse){// 第一步:确认是谁发的通知(把sender转回物业类型)CommunityPropertyproperty=senderasCommunityProperty;if(property==null){Console.WriteLine($"{Name}({HouseNumber}号):收到不明通知,忽略!");return;}// 第二步:获取通知详情(把e转回停水通知类型)WaterStopNoticenotice=easWaterStopNotice;if(notice==null){Console.WriteLine($"{Name}({HouseNumber}号):通知内容无效!");return;}// 第三步:根据通知做准备(不同业主反应不同)Console.WriteLine($"\n【{Name}({HouseNumber}号)】收到通知:");Console.WriteLine($" 通知方:{property.PropertyName}(联系电话:{property.Phone})");Console.WriteLine($" 停水时间:{notice.StopTime:yyyy-MM-dd HH:mm}");Console.WriteLine($" 停水时长:{notice.Duration}小时");Console.WriteLine($" 停水原因:{notice.Reason}");// 不同业主的个性化操作if(Name=="张三")Console.WriteLine($" 张三的操作:立刻去超市买2桶矿泉水!");elseif(Name=="李四")Console.WriteLine($" 李四的操作:现在就把衣服洗了,热水器加满水!");elseif(Name=="王五")Console.WriteLine($" 王五的操作:给爸妈打电话,提醒接水!");}}在这个类中,我们定义了一个方法:
publicvoidReceiveNotice(object?sender,EventArgse){......}这个方法接收object? sender, EventArgs e两个参数,没有返回值,这个方法的签名完全符合我们之前说的EventHandle委托的要求,即符合WaterStopEvent的要求。并且我们在方法内部也是通过object sender拿到了小区物业的姓名和电话,通过EventArgs获取了停水通知的各个详细信息。
2.4 执行整个流程
// 1. 创建发布者:幸福小区物业CommunityPropertyproperty=newCommunityProperty(){PropertyName="幸福小区物业",Phone="010-12345678"};// 2. 创建观察者:3个业主HouseOwnerzhangsan=newHouseOwner(){Name="张三",HouseNumber=101};HouseOwnerlisi=newHouseOwner(){Name="李四",HouseNumber=202};HouseOwnerwangwu=newHouseOwner(){Name="王五",HouseNumber=303};// 3. 业主订阅通知(去物业登记)property.WaterStopEvent+=zhangsan.ReceiveNotice;property.WaterStopEvent+=lisi.ReceiveNotice;property.WaterStopEvent+=wangwu.ReceiveNotice;// 4. 物业发通知(触发事件,传递参数)property.SendWaterStopNotice();这里我们首先创建了一个小区物业的实例,并且声明了他的名称和电话。然后创建了三个业主,声明了每个业主的姓名和房间号。随后,我们让每个业主订阅了物业的通知,即property.WaterStopEvent += zhangsan.ReceiveNotice;,由于WaterStopEvent是一个事件,外部只能订阅(+=)或者取消订阅(-=)。
订阅之后,这些方法会被放到WaterStopEvent事件中,等待调用。而最终,执行SendWaterStopNotice()方法后,这个事件会被调用。
比如当执行了SendWaterStopNotice()方法后,这个代码会触发事件WaterStopEvent.Invoke(this, notice);,将小区物业的实例property和停水消息WaterStopNotice传入事件,由于事件中的第一个方法是张三的,所以执行zhangsan.ReceiveNotice(object? sender, EventArgs e)。剩下两个方法也是以此类推。
执行结果:
【幸福小区物业】开始通知业主停水事宜! 【张三(101号)】收到通知: 通知方:幸福小区物业(联系电话:010-12345678) 停水时间:2025-12-15 08:00 停水时长:4小时 停水原因:小区主水管维修 张三的操作:立刻去超市买2桶矿泉水! 【李四(202号)】收到通知: 通知方:幸福小区物业(联系电话:010-12345678) 停水时间:2025-12-15 08:00 停水时长:4小时 停水原因:小区主水管维修 李四的操作:现在就把衣服洗了,热水器加满水! 【王五(303号)】收到通知: 通知方:幸福小区物业(联系电话:010-12345678) 停水时间:2025-12-15 08:00 停水时长:4小时 停水原因:小区主水管维修 王五的操作:给爸妈打电话,提醒接水!2.5 总结
| 生活场景 | 代码里的元素 | 核心作用 |
|---|---|---|
| 幸福小区物业 | CommunityProperty实例 | 发布者:控制通知时机、传参数 |
| 停水通知事件 | WaterStopEvent事件 | 订阅入口:仅允许 +=/-= |
| 停水详情(时间 / 时长) | WaterStopNotice(e 参数) | 传递具体的通知内容 |
| 物业自己(发通知的人) | this(sender 参数) | 告诉观察者 “谁发的通知” |
| 业主登记通知 | property.WaterStopEvent += 方法 | 订阅:加入通知列表 |
| 物业挨家挨户通知 | WaterStopEvent.Invoke(this, notice) | 触发事件:传递参数给每个业主 |
| 业主按通知做准备 | ReceiveNotice方法 | 响应:执行个性化逻辑 |
这个代码也是一个非常典型观察者模式,非常值得大家学习。