.NET进阶——深入理解委托(4)事件实战

在上一文我们已经大致介绍了关于事件的基础入门,现在我们来一个事件的实战,让大家更加深入的理解事件。这个代码也是观察者模式,如果对这个模式不清楚,可以看我上一个文章:.NET进阶------深入理解委托(3)事件入门为什么我要把事件放在委托这个专题里呢?主要的原因是事件是委托的高级封装。 换句 - 掘金

一、前景提要

1.1 EventHandler介绍

我们这个代码中的委托使用系统自带的EventHandler委托:

cs 复制代码
public delegate void EventHandler(object? sender, EventArgs e);

这个委托没有返回值,但需要传递两个参数,一个是object类型的,一个是EventArgs类型的,这两个参数分别是什么意思呢?其实,object类型就是调用者,剩下的EventArgs就是要传递的参数,我这么说可能有点不好理解,接着看下去你就懂了。

1.2 实战背景介绍

场景背景

你住在 "幸福小区",物业负责通知业主停水 / 停电等事宜:

  1. 第一步:业主订阅通知 张三、李四、王五去物业前台登记:"停水通知一定要告诉我!"(对应代码里的 += 订阅事件);
  2. 第二步:物业准备通知内容 物业确定:"明天(2025-12-15)上午 8 点到 12 点,小区停水 4 小时,原因是水管维修"(对应代码里的 WaterStopNotice 参数);
  3. 第三步:物业挨家挨户通知 物业人员上门,对每个登记的业主说:"我是幸福小区物业(sender),通知你:明天 8 点 - 12 点停水 4 小时(e 参数)"(对应代码里的 Invoke 传参);
  4. 第四步:业主响应通知 张三:"知道了,我囤点水";李四:"我提前洗好衣服";王五:"我提醒爸妈别忘接水"(对应代码里的 Buy 方法执行不同逻辑)。

二、代码详情

2.1 传递信息类:WaterStopNotice

cs 复制代码
    public class WaterStopNotice:EventArgs
    {
        public DateTime StopTime { get; set; } // 停水开始时间
        public int Duration { get; set; }      // 停水时长(小时)
        public string Reason { get; set; }     // 停水原因
    }

首先,这个类是我们需要传递的信息类,可以看到它是继承自EventArgs类,所以这个类是可以当成EventArgs类传递信息的。

2.2 小区物业类:CommunityProperty

cs 复制代码
public class CommunityProperty
{
    public string PropertyName { get; set; } // 物业名称(比如"幸福小区物业")
    public string Phone { get; set; }        // 物业联系电话

    // 定义"停水通知"事件(对外暴露,业主只能订阅/取消)
    public event EventHandler WaterStopEvent;

    // 物业内部的"发通知"方法(对应代码里的PublicShow)
    public void SendWaterStopNotice()
    {
        Console.WriteLine($"【{PropertyName}】开始通知业主停水事宜!");

        // 准备通知详情(e参数)
        WaterStopNotice notice = new WaterStopNotice()
        {
            StopTime = new DateTime(2025, 12, 15, 8, 0, 0),
            Duration = 4,
            Reason = "小区主水管维修"
        };

        // 触发事件:挨家挨户通知(传2个参数:物业自己+通知详情)
        WaterStopEvent.Invoke(this, notice);
    }
}

可以看到在这个类中,我们定义了两个属性,还定义了一个事件:

cs 复制代码
 public event EventHandler WaterStopEvent;

这个事件是把EventHanlder这个委托封装了,我们在1.1讨论了它的两个参数,一个是object类型,一个是EventArgs类型,现在我们把这个委托封装从事件后,这个事件WaterStopEvent也就跟该委托一样,只能接收带有两个参数object? senderEventArgs e,并且没有返回值的方法。

其次,我们还定义了触发方法SendWaterStopNotice,这个方法里我们首先创建了WaterStopNotice的实例对象,用这个实例对象来传递参数,并且我们还触发了事件 WaterStopEvent.Invoke(this, notice);,即调用SendWaterStopNotice方法,就可以触发绑定的事件。

触发这个事件时,我们传递了两个参数,一个是this,另一个是刚刚创建的实例noticenotice比较好理解,就是我们创建的用来通知小区业主的信息,但是前面的this是啥呢?

我们都知道this指向的是实例本身,也就是说这里的this传递的是我们后续要将校区业主类CommunityProperty实例化后的那个对象,那么为什么要传递这个实例对象呢?想一想,如果你是小区业主,当有人通知你要停水时,你是不是要确认一下对方是谁,只有确认对方是物业后,你才会选择相信 。那么这里也是一样的道理,由于我们的对象中有两个属性,一个是PropertyName物业名字,另一个是物业的电话Phone,所以我们要把这些信息也传递给小区业主,让他们知道发起这个消息的是谁,并且发起消息的人的电话。

2.2 物业类:HouseOwner

cs 复制代码
 public class HouseOwner
 {
     public string Name { get; set; } // 业主姓名
     public int HouseNumber { get; set; } // 门牌号

     // 接收通知的方法(对应代码里的Buy)
     // sender = 物业实例,e = 停水通知详情
     public void ReceiveNotice(object? sender, EventArgs e)
     {
         // 第一步:确认是谁发的通知(把sender转回物业类型)
         CommunityProperty property = sender as CommunityProperty;
         if (property == null)
         {
             Console.WriteLine($"{Name}({HouseNumber}号):收到不明通知,忽略!");
             return;
         }

         // 第二步:获取通知详情(把e转回停水通知类型)
         WaterStopNotice notice = e as WaterStopNotice;
         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桶矿泉水!");
         else if (Name == "李四")
             Console.WriteLine($"  李四的操作:现在就把衣服洗了,热水器加满水!");
         else if (Name == "王五")
             Console.WriteLine($"  王五的操作:给爸妈打电话,提醒接水!");
     }
 }

在这个类中,我们定义了一个方法:

cs 复制代码
    public void ReceiveNotice(object? sender, EventArgs e){
    ......
    }

这个方法接收object? sender, EventArgs e两个参数,没有返回值,这个方法的签名完全符合我们之前说的EventHandle委托的要求,即符合WaterStopEvent的要求。并且我们在方法内部也是通过object sender拿到了小区物业 的姓名和电话,通过EventArgs获取了停水通知的各个详细信息。

2.4 执行整个流程

cs 复制代码
// 1. 创建发布者:幸福小区物业
CommunityProperty property = new CommunityProperty()
{
    PropertyName = "幸福小区物业",
    Phone = "010-12345678"
};

// 2. 创建观察者:3个业主
HouseOwner zhangsan = new HouseOwner() { Name = "张三", HouseNumber = 101 };
HouseOwner lisi = new HouseOwner() { Name = "李四", HouseNumber = 202 };
HouseOwner wangwu = new HouseOwner() { 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 方法 响应:执行个性化逻辑

这个代码也是一个非常典型观察者模式,非常值得大家学习。

相关推荐
yuan199973 小时前
基于C#实现斑马打印机自动化包装打印
网络·c#·自动化
靓仔建3 小时前
在.NET Framework 4.7.2 使用Microsoft.Practices.EnterpriseLibrary.Data配置出错
c#·.net
MonkeyBananas4 小时前
C#项目引用log4net日志服务
c#
专注VB编程开发20年4 小时前
C# int*指向 int 的指针类型(unsafe 上下文)
java·开发语言·c#
Eiceblue5 小时前
通过 C# 将 RTF 文档转换为图片
开发语言·算法·c#
c#上位机5 小时前
halcon求图像灰度最大值和最小值——min_max_gray
图像处理·人工智能·计算机视觉·c#·上位机·halcon
极客智造6 小时前
深入解析.NET 中的 XDocument:解锁 XML 处理的高级特性
xml·.net
sali-tec14 小时前
C# 基于halcon的视觉工作流-章66 四目匹配
开发语言·人工智能·数码相机·算法·计算机视觉·c#
ITMr.罗21 小时前
深入理解EF Core更新机制(开发中因为省事遇到的问题)
服务器·数据库·c#·.net